These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * PrivateBin |
||
4 | * |
||
5 | * a zero-knowledge paste bin |
||
6 | * |
||
7 | * @link https://github.com/PrivateBin/PrivateBin |
||
8 | * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) |
||
9 | * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License |
||
10 | * @version 1.1 |
||
11 | */ |
||
12 | |||
13 | namespace PrivateBin; |
||
14 | |||
15 | /** |
||
16 | * I18n |
||
17 | * |
||
18 | * provides internationalization tools like translation, browser language detection, etc. |
||
19 | */ |
||
20 | class I18n |
||
21 | { |
||
22 | /** |
||
23 | * language |
||
24 | * |
||
25 | * @access protected |
||
26 | * @static |
||
27 | * @var string |
||
28 | */ |
||
29 | protected static $_language = 'en'; |
||
30 | |||
31 | /** |
||
32 | * language fallback |
||
33 | * |
||
34 | * @access protected |
||
35 | * @static |
||
36 | * @var string |
||
37 | */ |
||
38 | protected static $_languageFallback = 'en'; |
||
39 | |||
40 | /** |
||
41 | * language labels |
||
42 | * |
||
43 | * @access protected |
||
44 | * @static |
||
45 | * @var array |
||
46 | */ |
||
47 | protected static $_languageLabels = array(); |
||
48 | |||
49 | /** |
||
50 | * available languages |
||
51 | * |
||
52 | * @access protected |
||
53 | * @static |
||
54 | * @var array |
||
55 | */ |
||
56 | protected static $_availableLanguages = array(); |
||
57 | |||
58 | /** |
||
59 | * path to language files |
||
60 | * |
||
61 | * @access protected |
||
62 | * @static |
||
63 | * @var string |
||
64 | */ |
||
65 | protected static $_path = ''; |
||
66 | |||
67 | /** |
||
68 | * translation cache |
||
69 | * |
||
70 | * @access protected |
||
71 | * @static |
||
72 | * @var array |
||
73 | */ |
||
74 | protected static $_translations = array(); |
||
75 | |||
76 | /** |
||
77 | * translate a string, alias for translate() |
||
78 | * |
||
79 | * @access public |
||
80 | * @static |
||
81 | * @param string $messageId |
||
82 | * @param mixed $args one or multiple parameters injected into placeholders |
||
0 ignored issues
–
show
|
|||
83 | * @return string |
||
84 | */ |
||
85 | 81 | public static function _($messageId) |
|
0 ignored issues
–
show
|
|||
86 | { |
||
87 | 81 | return forward_static_call_array('self::translate', func_get_args()); |
|
88 | } |
||
89 | |||
90 | /** |
||
91 | * translate a string |
||
92 | * |
||
93 | * @access public |
||
94 | * @static |
||
95 | * @param string $messageId |
||
96 | * @param mixed $args one or multiple parameters injected into placeholders |
||
0 ignored issues
–
show
There is no parameter named
$args . Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not.
Loading history...
|
|||
97 | * @return string |
||
98 | */ |
||
99 | 81 | public static function translate($messageId) |
|
100 | { |
||
101 | 81 | if (empty($messageId)) { |
|
102 | 34 | return $messageId; |
|
103 | } |
||
104 | 81 | if (count(self::$_translations) === 0) { |
|
105 | 63 | self::loadTranslations(); |
|
106 | } |
||
107 | 81 | $messages = $messageId; |
|
108 | 81 | if (is_array($messageId)) { |
|
109 | 35 | $messageId = count($messageId) > 1 ? $messageId[1] : $messageId[0]; |
|
110 | } |
||
111 | 81 | if (!array_key_exists($messageId, self::$_translations)) { |
|
112 | 65 | self::$_translations[$messageId] = $messages; |
|
113 | } |
||
114 | 81 | $args = func_get_args(); |
|
115 | 81 | if (is_array(self::$_translations[$messageId])) { |
|
116 | 44 | $number = (int) $args[1]; |
|
117 | 44 | $key = self::_getPluralForm($number); |
|
118 | 44 | $max = count(self::$_translations[$messageId]) - 1; |
|
119 | 44 | if ($key > $max) { |
|
120 | $key = $max; |
||
121 | } |
||
122 | |||
123 | 44 | $args[0] = self::$_translations[$messageId][$key]; |
|
124 | 44 | $args[1] = $number; |
|
125 | } else { |
||
126 | 80 | $args[0] = self::$_translations[$messageId]; |
|
127 | } |
||
128 | 81 | return call_user_func_array('sprintf', $args); |
|
129 | } |
||
130 | |||
131 | /** |
||
132 | * loads translations |
||
133 | * |
||
134 | * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 |
||
135 | * |
||
136 | * @access public |
||
137 | * @static |
||
138 | */ |
||
139 | 73 | public static function loadTranslations() |
|
0 ignored issues
–
show
loadTranslations uses the super-global variable $_COOKIE which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
Loading history...
|
|||
140 | { |
||
141 | 73 | $availableLanguages = self::getAvailableLanguages(); |
|
142 | |||
143 | // check if the lang cookie was set and that language exists |
||
144 | if ( |
||
145 | 73 | array_key_exists('lang', $_COOKIE) && |
|
146 | 73 | ($key = array_search($_COOKIE['lang'], $availableLanguages)) !== false |
|
147 | ) { |
||
148 | 7 | $match = $availableLanguages[$key]; |
|
149 | } |
||
150 | // find a translation file matching the browsers language preferences |
||
151 | else { |
||
152 | 66 | $match = self::_getMatchingLanguage( |
|
153 | 66 | self::getBrowserLanguages(), $availableLanguages |
|
154 | ); |
||
155 | } |
||
156 | |||
157 | // load translations |
||
158 | 73 | self::$_language = $match; |
|
159 | 73 | self::$_translations = ($match == 'en') ? array() : json_decode( |
|
0 ignored issues
–
show
It seems like
$match == 'en' ? array()...atch . '.json')), true) of type * is incompatible with the declared type array of property $_translations .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||
160 | 16 | file_get_contents(self::_getPath($match . '.json')), |
|
161 | 73 | true |
|
162 | ); |
||
163 | 73 | } |
|
164 | |||
165 | /** |
||
166 | * get list of available translations based on files found |
||
167 | * |
||
168 | * @access public |
||
169 | * @static |
||
170 | * @return array |
||
171 | */ |
||
172 | 109 | public static function getAvailableLanguages() |
|
173 | { |
||
174 | 109 | if (count(self::$_availableLanguages) == 0) { |
|
175 | 95 | $i18n = dir(self::_getPath()); |
|
176 | 95 | while (false !== ($file = $i18n->read())) { |
|
177 | 95 | if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1) { |
|
178 | 95 | self::$_availableLanguages[] = $match[1]; |
|
179 | } |
||
180 | } |
||
181 | 95 | self::$_availableLanguages[] = 'en'; |
|
182 | } |
||
183 | 109 | return self::$_availableLanguages; |
|
184 | } |
||
185 | |||
186 | /** |
||
187 | * detect the clients supported languages and return them ordered by preference |
||
188 | * |
||
189 | * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 |
||
190 | * |
||
191 | * @access public |
||
192 | * @static |
||
193 | * @return array |
||
194 | */ |
||
195 | 66 | public static function getBrowserLanguages() |
|
0 ignored issues
–
show
getBrowserLanguages uses the super-global variable $_SERVER which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
Loading history...
|
|||
196 | { |
||
197 | 66 | $languages = array(); |
|
198 | 66 | if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) { |
|
199 | 11 | $languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])); |
|
200 | 11 | View Code Duplication | foreach ($languageRanges as $languageRange) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
201 | 11 | if (preg_match( |
|
202 | 11 | '/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/', |
|
203 | trim($languageRange), $match |
||
204 | )) { |
||
205 | 11 | if (!isset($match[2])) { |
|
206 | 5 | $match[2] = '1.0'; |
|
207 | } else { |
||
208 | 8 | $match[2] = (string) floatval($match[2]); |
|
209 | } |
||
210 | 11 | if (!isset($languages[$match[2]])) { |
|
211 | 11 | $languages[$match[2]] = array(); |
|
212 | } |
||
213 | 11 | $languages[$match[2]][] = strtolower($match[1]); |
|
214 | } |
||
215 | } |
||
216 | 11 | krsort($languages); |
|
217 | } |
||
218 | 66 | return $languages; |
|
219 | } |
||
220 | |||
221 | /** |
||
222 | * get currently loaded language |
||
223 | * |
||
224 | * @access public |
||
225 | * @static |
||
226 | * @return string |
||
227 | */ |
||
228 | 2 | public static function getLanguage() |
|
229 | { |
||
230 | 2 | return self::$_language; |
|
231 | } |
||
232 | |||
233 | /** |
||
234 | * get list of language labels |
||
235 | * |
||
236 | * Only for given language codes, otherwise all labels. |
||
237 | * |
||
238 | * @access public |
||
239 | * @static |
||
240 | * @param array $languages |
||
241 | * @return array |
||
242 | */ |
||
243 | 36 | public static function getLanguageLabels($languages = array()) |
|
244 | { |
||
245 | 36 | $file = self::_getPath('languages.json'); |
|
246 | 36 | if (count(self::$_languageLabels) == 0 && is_readable($file)) { |
|
247 | 35 | self::$_languageLabels = json_decode(file_get_contents($file), true); |
|
0 ignored issues
–
show
It seems like
json_decode(file_get_contents($file), true) of type * is incompatible with the declared type array of property $_languageLabels .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||
248 | } |
||
249 | 36 | if (count($languages) == 0) { |
|
250 | return self::$_languageLabels; |
||
251 | } |
||
252 | 36 | return array_intersect_key(self::$_languageLabels, array_flip($languages)); |
|
253 | } |
||
254 | |||
255 | /** |
||
256 | * set the default language |
||
257 | * |
||
258 | * @access public |
||
259 | * @static |
||
260 | * @param string $lang |
||
261 | */ |
||
262 | 94 | public static function setLanguageFallback($lang) |
|
263 | { |
||
264 | 94 | if (in_array($lang, self::getAvailableLanguages())) { |
|
265 | 2 | self::$_languageFallback = $lang; |
|
266 | } |
||
267 | 94 | } |
|
268 | |||
269 | /** |
||
270 | * get language file path |
||
271 | * |
||
272 | * @access protected |
||
273 | * @static |
||
274 | * @param string $file |
||
275 | * @return string |
||
276 | */ |
||
277 | 107 | protected static function _getPath($file = '') |
|
278 | { |
||
279 | 107 | if (strlen(self::$_path) == 0) { |
|
280 | 95 | self::$_path = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'i18n'; |
|
281 | } |
||
282 | 107 | return self::$_path . (strlen($file) ? DIRECTORY_SEPARATOR . $file : ''); |
|
283 | } |
||
284 | |||
285 | /** |
||
286 | * determines the plural form to use based on current language and given number |
||
287 | * |
||
288 | * From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html |
||
289 | * |
||
290 | * @access protected |
||
291 | * @static |
||
292 | * @param int $n |
||
293 | * @return int |
||
294 | */ |
||
295 | 44 | protected static function _getPluralForm($n) |
|
296 | { |
||
297 | 44 | switch (self::$_language) { |
|
298 | 44 | case 'fr': |
|
299 | 41 | case 'oc': |
|
300 | 40 | case 'zh': |
|
301 | 5 | return $n > 1 ? 1 : 0; |
|
302 | 39 | case 'pl': |
|
303 | 1 | return $n == 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); |
|
304 | 38 | case 'ru': |
|
305 | 1 | return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); |
|
306 | 37 | case 'sl': |
|
307 | 1 | return $n % 100 == 1 ? 1 : ($n % 100 == 2 ? 2 : ($n % 100 == 3 || $n % 100 == 4 ? 3 : 0)); |
|
308 | // de, en, es, it, no, pt |
||
309 | default: |
||
310 | 36 | return $n != 1 ? 1 : 0; |
|
311 | } |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * compares two language preference arrays and returns the preferred match |
||
316 | * |
||
317 | * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 |
||
318 | * |
||
319 | * @access protected |
||
320 | * @static |
||
321 | * @param array $acceptedLanguages |
||
322 | * @param array $availableLanguages |
||
323 | * @return string |
||
324 | */ |
||
325 | 66 | protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) |
|
326 | { |
||
327 | 66 | $matches = array(); |
|
328 | 66 | $any = false; |
|
329 | 66 | foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues) { |
|
330 | 11 | $acceptedQuality = floatval($acceptedQuality); |
|
331 | 11 | if ($acceptedQuality === 0.0) { |
|
332 | continue; |
||
333 | } |
||
334 | 11 | foreach ($availableLanguages as $availableValue) { |
|
335 | 11 | $availableQuality = 1.0; |
|
336 | 11 | foreach ($acceptedValues as $acceptedValue) { |
|
337 | 11 | if ($acceptedValue === '*') { |
|
338 | 1 | $any = true; |
|
339 | } |
||
340 | 11 | $matchingGrade = self::_matchLanguage($acceptedValue, $availableValue); |
|
341 | 11 | if ($matchingGrade > 0) { |
|
342 | 8 | $q = (string) ($acceptedQuality * $availableQuality * $matchingGrade); |
|
343 | 8 | if (!isset($matches[$q])) { |
|
344 | 8 | $matches[$q] = array(); |
|
345 | } |
||
346 | 8 | if (!in_array($availableValue, $matches[$q])) { |
|
347 | 11 | $matches[$q][] = $availableValue; |
|
348 | } |
||
349 | } |
||
350 | } |
||
351 | } |
||
352 | } |
||
353 | 66 | if (count($matches) === 0 && $any) { |
|
354 | 1 | if (count($availableLanguages) > 0) { |
|
355 | 1 | $matches['1.0'] = $availableLanguages; |
|
356 | } |
||
357 | } |
||
358 | 66 | if (count($matches) === 0) { |
|
359 | 57 | return self::$_languageFallback; |
|
360 | } |
||
361 | 9 | krsort($matches); |
|
362 | 9 | $topmatches = current($matches); |
|
363 | 9 | return current($topmatches); |
|
364 | } |
||
365 | |||
366 | /** |
||
367 | * compare two language IDs and return the degree they match |
||
368 | * |
||
369 | * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 |
||
370 | * |
||
371 | * @access protected |
||
372 | * @static |
||
373 | * @param string $a |
||
374 | * @param string $b |
||
375 | * @return float |
||
376 | */ |
||
377 | 11 | protected static function _matchLanguage($a, $b) |
|
378 | { |
||
379 | 11 | $a = explode('-', $a); |
|
380 | 11 | $b = explode('-', $b); |
|
381 | 11 | for ($i = 0, $n = min(count($a), count($b)); $i < $n; ++$i) { |
|
382 | 11 | if ($a[$i] !== $b[$i]) { |
|
383 | 11 | break; |
|
384 | } |
||
385 | } |
||
386 | 11 | return $i === 0 ? 0 : (float) $i / count($a); |
|
387 | } |
||
388 | } |
||
389 |
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italy
is not defined by the methodfinale(...)
.The most likely cause is that the parameter was removed, but the annotation was not.