Language::getTranslateHelpInfo()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 13
ccs 2
cts 2
cp 1
rs 9.9666
c 0
b 0
f 0
cc 4
nc 4
nop 2
crap 4
1
<?php
2
3
namespace App;
4
5
/**
6
 * Language basic class.
7
 *
8
 * @package App
9
 *
10
 * @copyright YetiForce S.A.
11
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
12
 * @author    Mariusz Krzaczkowski <[email protected]>
13
 * @author    Adrian Koń <[email protected]>
14
 * @author    Radosław Skrzypczak <[email protected]>
15
 */
16
class Language
17
{
18
	/**
19
	 * Default language code.
20
	 */
21
	public const DEFAULT_LANG = 'en-US';
22
23
	/**
24
	 * Allowed types of language variables.
25
	 */
26
	const LANG_TYPE = ['php', 'js'];
27
28
	/**
29
	 * Language files format.
30
	 */
31
	const FORMAT = 'json';
32
	/**
33
	 * Custom language directory.
34
	 *
35
	 * @var string
36
	 */
37
	public static $customDirectory = 'custom';
38
39
	/** @var string Current language. */
40
	private static $language = '';
41
42
	/** @var string Temporary language. */
43
	private static $temporaryLanguage = '';
44
45
	/**
46
	 * Short current language.
47
	 *
48
	 * @var bool|string
49
	 */
50
	private static $shortLanguage = false;
51
52
	/**
53
	 * Pluralize cache.
54
	 *
55
	 * @var array
56
	 */
57
	private static $pluralizeCache = [];
58
59
	/**
60
	 * Contains module language translations.
61
	 *
62
	 * @var array
63
	 */
64
	protected static $languageContainer;
65
66
	/**
67
	 * Function that returns current language.
68
	 *
69
	 * @return string -
70
	 */
71
	public static function getLanguage()
72
	{
73
		if (static::$temporaryLanguage) {
0 ignored issues
show
Bug introduced by
Since $temporaryLanguage is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $temporaryLanguage to at least protected.
Loading history...
74
			return static::$temporaryLanguage;
75
		}
76 90
		if (static::$language) {
0 ignored issues
show
Bug introduced by
Since $language is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $language to at least protected.
Loading history...
77
			return static::$language;
78 90
		}
79 22
		if (!empty(\App\Session::get('language'))) {
80
			$language = \App\Session::get('language');
81 71
		} else {
82 71
			$language = User::getCurrentUserModel()->getDetail('language');
83
		}
84 1
		return static::$language = empty($language) ? \App\Config::main('default_language') : $language;
85
	}
86
87 1
	/**
88
	 * Get IETF language tag.
89 1
	 *
90
	 * @see https://en.wikipedia.org/wiki/IETF_language_tag
91
	 *
92
	 * @param mixed $separator
93
	 *
94
	 * @return string
95
	 */
96
	public static function getLanguageTag($separator = '_')
97
	{
98
		return str_replace('-', $separator, static::getLanguage());
99
	}
100
101
	/**
102
	 * Set temporary language.
103
	 *
104
	 * @param string $language
105
	 */
106
	public static function setTemporaryLanguage(string $language)
107
	{
108
		static::$temporaryLanguage = $language;
0 ignored issues
show
Bug introduced by
Since $temporaryLanguage is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $temporaryLanguage to at least protected.
Loading history...
109
	}
110
111 17
	/**
112
	 * Clear temporary language.
113 17
	 *
114 17
	 * @return string
115
	 */
116
	public static function clearTemporaryLanguage()
117
	{
118
		static::$temporaryLanguage = false;
0 ignored issues
show
Bug introduced by
Since $temporaryLanguage is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $temporaryLanguage to at least protected.
Loading history...
Documentation Bug introduced by
The property $temporaryLanguage was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
119
	}
120
121 29
	/**
122
	 * Function that returns current language short name.
123 29
	 *
124 29
	 * @return string
125
	 */
126
	public static function getShortLanguageName()
127
	{
128
		if (static::$shortLanguage) {
0 ignored issues
show
Bug introduced by
Since $shortLanguage is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $shortLanguage to at least protected.
Loading history...
129
			return static::$shortLanguage;
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::shortLanguage also could return the type true which is incompatible with the documented return type string.
Loading history...
130
		}
131 2
		preg_match('/^[a-z]+/i', static::getLanguage(), $match);
132
		return static::$shortLanguage = (empty($match[0])) ? \Locale::getPrimaryLanguage(self::DEFAULT_LANG) : $match[0];
133 2
	}
134 2
135
	/**
136 1
	 * Function that returns region for language prefix.
137 1
	 *
138
	 * @param string|null $lang
139
	 *
140
	 * @return string
141
	 */
142
	public static function getLanguageRegion(?string $lang = null): string
143
	{
144
		if (!$lang) {
145
			$lang = static::getLanguage();
146
		}
147
		return \Locale::parseLocale($lang)['region'] ?? substr($lang, -2);
148
	}
149
150 83
	/**
151
	 * Functions that gets translated string.
152 83
	 *
153 1
	 * @param string      $key              - string which need to be translated
154
	 * @param string      $moduleName       - module scope in which the translation need to be check
155 83
	 * @param string|null $language         - language of translation
156 83
	 * @param bool        $encode           - When no translation was found do encode the output
157
	 * @param string      $secondModuleName - Additional module name to be translated when not in $moduleName
158 83
	 *
159 1
	 * @return string - translated string
160 1
	 */
161
	public static function translate(string $key, string $moduleName = '_Base', ?string $language = null, bool $encode = true, ?string $secondModuleName = null)
162 83
	{
163 1
		if (empty($key)) { // nothing to translate
164
			return $key;
165 83
		}
166
		if (!$language || ($language && 5 !== \strlen($language))) {
167 83
			$language = static::getLanguage();
168 83
		}
169 82
		if (\is_array($moduleName)) {
0 ignored issues
show
introduced by
The condition is_array($moduleName) is always false.
Loading history...
170 82
			Log::warning('Invalid module name - module: ' . var_export($moduleName, true));
171
			return $key;
172
		}
173
		if (is_numeric($moduleName)) { // ok, we have a tab id, lets turn it into name
174
			$moduleName = Module::getModuleName($moduleName);
0 ignored issues
show
Bug introduced by
$moduleName of type string is incompatible with the type integer expected by parameter $tabId of App\Module::getModuleName(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
			$moduleName = Module::getModuleName(/** @scrutinizer ignore-type */ $moduleName);
Loading history...
175 13
		} else {
176 2
			$moduleName = str_replace([':', '.'], [\DIRECTORY_SEPARATOR, \DIRECTORY_SEPARATOR], $moduleName);
177 2
		}
178 2
		static::loadLanguageFile($language, $moduleName);
179 1
		if (isset(static::$languageContainer[$language][$moduleName]['php'][$key])) {
180 1
			if ($encode) {
181
				return \nl2br(Purifier::encodeHtml(static::$languageContainer[$language][$moduleName]['php'][$key]));
182
			}
183
			return \nl2br(static::$languageContainer[$language][$moduleName]['php'][$key]);
184
		}
185 13
		if ($secondModuleName) {
186 13
			$secondModuleName = str_replace([':', '.'], [\DIRECTORY_SEPARATOR, \DIRECTORY_SEPARATOR], $secondModuleName);
187 11
			static::loadLanguageFile($language, $secondModuleName);
188 11
			if (isset(static::$languageContainer[$language][$secondModuleName]['php'][$key])) {
189
				if ($encode) {
190
					return \nl2br(Purifier::encodeHtml(static::$languageContainer[$language][$secondModuleName]['php'][$key]));
191
				}
192 4
				return \nl2br(static::$languageContainer[$language][$secondModuleName]['php'][$key]);
193
			}
194
		}
195 4
		// Lookup for the translation in base module, in case of sub modules, before ending up with common strings
196 4
		if (0 === strpos($moduleName, 'Settings')) {
197
			$base = 'Settings' . \DIRECTORY_SEPARATOR . '_Base';
198
			static::loadLanguageFile($language, $base);
199
			if (isset(static::$languageContainer[$language][$base]['php'][$key])) {
200
				if ($encode) {
201
					return \nl2br(Purifier::encodeHtml(static::$languageContainer[$language][$base]['php'][$key]));
202
				}
203
				return \nl2br(static::$languageContainer[$language][$base]['php'][$key]);
204
			}
205
		}
206
		static::loadLanguageFile($language);
207
		if (isset(static::$languageContainer[$language]['_Base']['php'][$key])) {
208
			if ($encode) {
209
				return \nl2br(Purifier::encodeHtml(static::$languageContainer[$language]['_Base']['php'][$key]));
210
			}
211
			return \nl2br(static::$languageContainer[$language]['_Base']['php'][$key]);
212
		}
213
		if (\App\Config::performance('recursiveTranslate') && static::DEFAULT_LANG !== $language) {
214
			return static::translate($key, $moduleName, static::DEFAULT_LANG, $encode, $secondModuleName);
215
		}
216
		\App\Log::info("Cannot translate this: '$key' for module '$moduleName', lang: $language");
217
218
		return $encode ? Purifier::encodeHtml($key) : $key;
219
	}
220
221
	/**
222
	 * Functions get translate help info.
223
	 *
224
	 * @param \Vtiger_Field_Model $fieldModel
225
	 * @param string              $view
226
	 *
227
	 * @return string
228
	 */
229
	public static function getTranslateHelpInfo(\Vtiger_Field_Model $fieldModel, string $view): string
230
	{
231 1
		$translate = '';
232
		if (\in_array($view, explode(',', $fieldModel->get('helpinfo')))) {
233 1
			$label = $fieldModel->getFieldLabel();
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

233
			$label = /** @scrutinizer ignore-deprecated */ $fieldModel->getFieldLabel();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
234
			$key = "{$fieldModel->getModuleName()}|$label";
235
			if (($translated = self::translateSingleMod($key, 'Other:HelpInfo')) !== $key) {
236
				$translate = $translated;
237
			} elseif (($translated = self::translateSingleMod($label, 'Other:HelpInfo')) !== $label) {
238
				$translate = $translated;
239
			}
240
		}
241
		return $translate;
242
	}
243
244 1
	/**
245
	 * Functions that gets translated string with encoding html.
246 1
	 *
247 1
	 * @param string $key             - string which need to be translated
248 1
	 * @param string $moduleName      - module scope in which the translation need to be check
249 1
	 * @param mixed  $currentLanguage
250
	 *
251 1
	 * @return string - translated string with encoding html
252
	 */
253
	public static function translateEncodeHtml($key, $moduleName = '_Base', $currentLanguage = false)
254
	{
255
		return \App\Purifier::encodeHtml(static::translate($key, $moduleName, $currentLanguage));
256
	}
257
258
	/**
259
	 * Functions that gets translated string by $args.
260
	 *
261
	 * @param string $key        - string which need to be translated
262
	 * @param string $moduleName - module scope in which the translation need to be check
263
	 *
264
	 * @return string - translated string
265
	 */
266 2
	public static function translateArgs($key, $moduleName = '_Base')
267
	{
268 2
		$formattedString = static::translate($key, $moduleName);
269
		$args = \array_slice(\func_get_args(), 2);
270
		if (\is_array($args) && !empty($args)) {
271 2
			$formattedString = \call_user_func_array('vsprintf', [$formattedString, $args]);
272
		}
273 2
		return $formattedString;
274
	}
275
276
	/**
277
	 * Functions that gets pluralized translated string.
278
	 *
279
	 * @param string $key        String which need to be translated
280
	 * @param string $moduleName Module scope in which the translation need to be check
281
	 * @param int    $count      Quantityu for plural determination
282
	 *
283
	 * @see https://www.i18next.com/plurals.html
284
	 * @see https://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms#pluralforms-list
285 3
	 *
286
	 * @return string
287 3
	 */
288 3
	public static function translatePluralized($key, $moduleName, $count)
289
	{
290 3
		if (isset(static::$pluralizeCache[$count])) {
0 ignored issues
show
Bug introduced by
Since $pluralizeCache is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $pluralizeCache to at least protected.
Loading history...
291 3
			$postfix = static::$pluralizeCache[$count];
292 3
		} else {
293 2
			$postfix = static::getPluralized((int) $count);
294
		}
295 1
		return vsprintf(static::translate($key . $postfix, $moduleName), [$count]);
296
	}
297
298
	/**
299
	 * Translation function based on only one file.
300
	 *
301
	 * @param string      $key
302
	 * @param string      $moduleName
303
	 * @param bool|string $language
304
	 * @param mixed       $encode
305 1
	 *
306
	 * @return string
307 1
	 */
308
	public static function translateSingleMod($key, $moduleName = '_Base', $language = false, $encode = true)
309
	{
310
		if (!$language) {
311
			$language = static::getLanguage();
312
		}
313
		$moduleName = str_replace([':', '.'], [\DIRECTORY_SEPARATOR, \DIRECTORY_SEPARATOR], $moduleName);
314
		static::loadLanguageFile($language, $moduleName);
315
		if (isset(static::$languageContainer[$language][$moduleName]['php'][$key])) {
316
			if ($encode) {
317 1
				return Purifier::encodeHtml(static::$languageContainer[$language][$moduleName]['php'][$key]);
318
			}
319 1
			return static::$languageContainer[$language][$moduleName]['php'][$key];
320
		}
321
		if (\App\Config::performance('recursiveTranslate') && static::DEFAULT_LANG !== $language) {
322
			return static::translateSingleMod($key, $moduleName, static::DEFAULT_LANG, $encode);
323
		}
324
		return $encode ? Purifier::encodeHtml($key) : $key;
325
	}
326
327
	/**
328 88
	 * Get singular module name.
329
	 *
330 88
	 * @param string $moduleName
331 11
	 *
332
	 * @return string
333
	 */
334 11
	public static function getSingularModuleName($moduleName)
335 11
	{
336 11
		return "SINGLE_$moduleName";
337 11
	}
338 10
339
	/**
340 11
	 * Translate singular module name.
341 11
	 *
342
	 * @param string $moduleName
343
	 *
344
	 * @return string
345
	 */
346
	public static function translateSingularModuleName($moduleName)
347
	{
348
		return static::translate("SINGLE_$moduleName", $moduleName);
349 11
	}
350 3
351
	/**
352 11
	 * Load language file.
353
	 *
354
	 * @param string $language
355 88
	 * @param string $moduleName
356
	 */
357
	public static function loadLanguageFile($language, $moduleName = '_Base')
358
	{
359
		if (!isset(static::$languageContainer[$language][$moduleName])) {
360
			if (Cache::has('LanguageFiles', $language . $moduleName)) {
361
				static::$languageContainer[$language][$moduleName] = Cache::get('LanguageFiles', $language . $moduleName);
362
			} else {
363
				static::$languageContainer[$language][$moduleName] = [];
364
				$file = \DIRECTORY_SEPARATOR . 'languages' . \DIRECTORY_SEPARATOR . $language . \DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT;
365 1
				$langFile = ROOT_DIRECTORY . $file;
366
				if (file_exists($langFile)) {
367 1
					static::$languageContainer[$language][$moduleName] = Json::decode(file_get_contents($langFile), true) ?? [];
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type integer expected by parameter $objectDecodeType of App\Json::decode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

367
					static::$languageContainer[$language][$moduleName] = Json::decode(file_get_contents($langFile), /** @scrutinizer ignore-type */ true) ?? [];
Loading history...
Security File Exposure introduced by
$langFile can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

5 paths for user data to reach this point

  1. Path: Read from $_REQUEST, and Request::__construct() is called in app/Request.php on line 728
  1. Read from $_REQUEST, and Request::__construct() is called
    in app/Request.php on line 728
  2. Enters via parameter $rawValues
    in app/Request.php on line 110
  3. $rawValues is assigned to property Request::$rawValues
    in app/Request.php on line 112
  4. Read from property Request::$rawValues, and Data is passed through purifyByType(), and $this->purifiedValuesByType[$key][$type] = App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is returned
    in app/Request.php on line 170
  5. Time::formatToDB() is called
    in modules/Settings/BusinessHours/actions/Save.php on line 30
  6. Enters via parameter $time
    in app/Fields/Time.php on line 41
  7. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  8. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  9. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  10. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime) is assigned to $date_value
    in include/fields/DateTimeField.php on line 299
  11. Data is passed through convertToUserFormat(), and self::convertToUserFormat($date_value) is returned
    in include/fields/DateTimeField.php on line 308
  12. $this->getDisplayDate($user) . ' ' . $this->getDisplayTime($user) is returned
    in include/fields/DateTimeField.php on line 72
  13. new DateTimeField($value)->getDisplayDateTimeValue() is returned
    in app/Fields/DateTime.php on line 35
  14. Mailer::sendFromTemplate() is called
    in modules/Users/actions/LoginForgotPassword.php on line 41
  15. Enters via parameter $params
    in app/Mailer.php on line 99
  16. TextParser::setLanguage() is called
    in app/Mailer.php on line 123
  17. Enters via parameter $name
    in app/TextParser.php on line 318
  18. $name is assigned to property TextParser::$language
    in app/TextParser.php on line 320
  19. Read from property TextParser::$language, and Language::translate() is called
    in app/TextParser.php on line 736
  20. Enters via parameter $language
    in app/Language.php on line 161
  21. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  22. Enters via parameter $language
    in app/Language.php on line 357
  23. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  24. App\ROOT_DIRECTORY . $file is assigned to $langFile
    in app/Language.php on line 365
  2. Path: Read from $_REQUEST, and Request::__construct() is called in api/webservice/Core/Request.php on line 56
  1. Read from $_REQUEST, and Request::__construct() is called
    in api/webservice/Core/Request.php on line 56
  2. Enters via parameter $rawValues
    in app/Request.php on line 110
  3. $rawValues is assigned to property Request::$rawValues
    in app/Request.php on line 112
  4. Read from property Request::$rawValues, and Data is passed through purifyByType(), and $this->purifiedValuesByType[$key][$type] = App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is returned
    in app/Request.php on line 170
  5. Time::formatToDB() is called
    in modules/Settings/BusinessHours/actions/Save.php on line 29
  6. Enters via parameter $time
    in app/Fields/Time.php on line 41
  7. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  8. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  9. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  10. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime) is assigned to $date_value
    in include/fields/DateTimeField.php on line 299
  11. Data is passed through convertToUserFormat(), and self::convertToUserFormat($date_value) is returned
    in include/fields/DateTimeField.php on line 308
  12. $this->getDisplayDate($user) . ' ' . $this->getDisplayTime($user) is returned
    in include/fields/DateTimeField.php on line 72
  13. new DateTimeField($value)->getDisplayDateTimeValue() is returned
    in app/Fields/DateTime.php on line 35
  14. Mailer::sendFromTemplate() is called
    in modules/Users/actions/LoginForgotPassword.php on line 41
  15. Enters via parameter $params
    in app/Mailer.php on line 99
  16. TextParser::setLanguage() is called
    in app/Mailer.php on line 123
  17. Enters via parameter $name
    in app/TextParser.php on line 318
  18. $name is assigned to property TextParser::$language
    in app/TextParser.php on line 320
  19. Read from property TextParser::$language, and Language::translate() is called
    in app/TextParser.php on line 722
  20. Enters via parameter $language
    in app/Language.php on line 161
  21. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  22. Enters via parameter $language
    in app/Language.php on line 357
  23. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  24. App\ROOT_DIRECTORY . $file is assigned to $langFile
    in app/Language.php on line 365
  3. Path: DateTimeField::__construct() is called in app/Fields/Time.php on line 44
  1. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  2. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  3. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  4. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime) is assigned to $date_value
    in include/fields/DateTimeField.php on line 299
  5. Data is passed through convertToUserFormat(), and self::convertToUserFormat($date_value) is returned
    in include/fields/DateTimeField.php on line 308
  6. new DateTimeField($value)->getDisplayDate() is returned
    in app/Fields/Date.php on line 113
  7. $convertTimeZone ? App\Fields\Date::formatToDisplay(date('Y-m-d'), false) : date('Y-m-d') is assigned to $date
    in app/Fields/Time.php on line 43
  8. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  9. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  10. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  11. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime) is assigned to $date_value
    in include/fields/DateTimeField.php on line 299
  12. Data is passed through convertToUserFormat(), and self::convertToUserFormat($date_value) is returned
    in include/fields/DateTimeField.php on line 308
  13. $this->getDisplayDate($user) . ' ' . $this->getDisplayTime($user) is returned
    in include/fields/DateTimeField.php on line 72
  14. new DateTimeField($value)->getDisplayDateTimeValue() is returned
    in app/Fields/DateTime.php on line 35
  15. Mailer::sendFromTemplate() is called
    in modules/Users/actions/LoginForgotPassword.php on line 41
  16. Enters via parameter $params
    in app/Mailer.php on line 99
  17. TextParser::setLanguage() is called
    in app/Mailer.php on line 123
  18. Enters via parameter $name
    in app/TextParser.php on line 318
  19. $name is assigned to property TextParser::$language
    in app/TextParser.php on line 320
  20. Read from property TextParser::$language, and Language::translate() is called
    in app/TextParser.php on line 700
  21. Enters via parameter $language
    in app/Language.php on line 161
  22. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  23. Enters via parameter $language
    in app/Language.php on line 357
  24. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  25. App\ROOT_DIRECTORY . $file is assigned to $langFile
    in app/Language.php on line 365
  4. Path: Request::set() is called in modules/Vtiger/models/Widget.php on line 615
  1. Request::set() is called
    in modules/Vtiger/models/Widget.php on line 615
  2. Enters via parameter $value
    in app/Request.php on line 594
  3. $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByInteger
    in app/Request.php on line 599
  4. Read from property Request::$purifiedValuesByInteger, and $this->purifiedValuesByInteger[$key] is returned
    in app/Request.php on line 203
  5. $request->getInteger('dashboardId') is assigned to $dashboardId
    in modules/Vtiger/models/Widget.php on line 608
  6. Request::set() is called
    in modules/Vtiger/models/Widget.php on line 615
  7. Enters via parameter $value
    in app/Request.php on line 594
  8. $this->purifiedValuesByInteger[$key] = $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByGet
    in app/Request.php on line 599
  9. Read from property Request::$purifiedValuesByGet, and $this->purifiedValuesByGet[$key] is returned
    in app/Request.php on line 129
  10. Users_Module_Model::checkUserName() is called
    in modules/Users/actions/Save.php on line 56
  11. Enters via parameter $userName
    in modules/Users/models/Module.php on line 193
  12. Language::translate() is called
    in app/Language.php on line 214
  13. Enters via parameter $language
    in app/Language.php on line 161
  14. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  15. Enters via parameter $language
    in app/Language.php on line 357
  16. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  17. App\ROOT_DIRECTORY . $file is assigned to $langFile
    in app/Language.php on line 365
  5. Path: Read tainted data from array, and Data is passed through explode(), and explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) is assigned to $code in install/views/Index.php on line 74
  1. Read tainted data from array, and Data is passed through explode(), and explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) is assigned to $code
    in install/views/Index.php on line 74
  2. $code is assigned to $lang
    in install/views/Index.php on line 79
  3. Request::set() is called
    in install/views/Index.php on line 87
  4. Enters via parameter $value
    in app/Request.php on line 594
  5. $this->purifiedValuesByInteger[$key] = $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByGet
    in app/Request.php on line 599
  6. Read from property Request::$purifiedValuesByGet, and $this->purifiedValuesByGet[$key] is returned
    in app/Request.php on line 129
  7. Users_Module_Model::checkUserName() is called
    in modules/Users/actions/Save.php on line 56
  8. Enters via parameter $userName
    in modules/Users/models/Module.php on line 193
  9. Language::translate() is called
    in app/Language.php on line 214
  10. Enters via parameter $language
    in app/Language.php on line 161
  11. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  12. Enters via parameter $language
    in app/Language.php on line 357
  13. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  14. App\ROOT_DIRECTORY . $file is assigned to $langFile
    in app/Language.php on line 365

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
368 1
				}
369 1
				$langCustomFile = ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . static::$customDirectory . $file;
370
				if (file_exists($langCustomFile)) {
371
					$translation = Json::decode(file_get_contents($langCustomFile), true) ?? [];
0 ignored issues
show
Security File Exposure introduced by
$langCustomFile can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

5 paths for user data to reach this point

  1. Path: Read from $_REQUEST, and Request::__construct() is called in app/Request.php on line 728
  1. Read from $_REQUEST, and Request::__construct() is called
    in app/Request.php on line 728
  2. Enters via parameter $rawValues
    in app/Request.php on line 110
  3. $rawValues is assigned to property Request::$rawValues
    in app/Request.php on line 112
  4. Read from property Request::$rawValues, and Data is passed through purifyByType(), and $this->purifiedValuesByType[$key][$type] = App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is returned
    in app/Request.php on line 170
  5. Time::formatToDB() is called
    in modules/Settings/BusinessHours/actions/Save.php on line 29
  6. Enters via parameter $time
    in app/Fields/Time.php on line 41
  7. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  8. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  9. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  10. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime) is assigned to $date_value
    in include/fields/DateTimeField.php on line 299
  11. Data is passed through convertToUserFormat(), and self::convertToUserFormat($date_value) is returned
    in include/fields/DateTimeField.php on line 308
  12. $this->getDisplayDate($user) . ' ' . $this->getDisplayTime($user) is returned
    in include/fields/DateTimeField.php on line 72
  13. new DateTimeField($value)->getDisplayDateTimeValue() is returned
    in app/Fields/DateTime.php on line 35
  14. Mailer::sendFromTemplate() is called
    in modules/Users/actions/LoginForgotPassword.php on line 41
  15. Enters via parameter $params
    in app/Mailer.php on line 99
  16. TextParser::setLanguage() is called
    in app/Mailer.php on line 123
  17. Enters via parameter $name
    in app/TextParser.php on line 318
  18. $name is assigned to property TextParser::$language
    in app/TextParser.php on line 320
  19. Read from property TextParser::$language, and Language::translate() is called
    in app/TextParser.php on line 736
  20. Enters via parameter $language
    in app/Language.php on line 161
  21. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  22. Enters via parameter $language
    in app/Language.php on line 357
  23. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  24. App\ROOT_DIRECTORY . DIRECTORY_SEPARATOR . static::customDirectory . $file is assigned to $langCustomFile
    in app/Language.php on line 369
  2. Path: Read from $_REQUEST, and Request::__construct() is called in api/webservice/Core/Request.php on line 56
  1. Read from $_REQUEST, and Request::__construct() is called
    in api/webservice/Core/Request.php on line 56
  2. Enters via parameter $rawValues
    in app/Request.php on line 110
  3. $rawValues is assigned to property Request::$rawValues
    in app/Request.php on line 112
  4. Read from property Request::$rawValues, and $this->rawValues[$key] is returned
    in app/Request.php on line 463
  5. $request->getRaw('inventory') is assigned to $rawInventory
    in modules/Vtiger/models/Record.php on line 1323
  6. Request::set() is called
    in modules/Vtiger/models/Record.php on line 1334
  7. Enters via parameter $value
    in app/Request.php on line 594
  8. $this->purifiedValuesByInteger[$key] = $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByGet
    in app/Request.php on line 599
  9. Read from property Request::$purifiedValuesByGet, and $this->purifiedValuesByGet[$key] is returned
    in app/Request.php on line 129
  10. Users_Module_Model::checkUserName() is called
    in modules/Users/actions/Save.php on line 56
  11. Enters via parameter $userName
    in modules/Users/models/Module.php on line 193
  12. Language::translate() is called
    in app/Language.php on line 214
  13. Enters via parameter $language
    in app/Language.php on line 161
  14. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  15. Enters via parameter $language
    in app/Language.php on line 357
  16. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  17. App\ROOT_DIRECTORY . DIRECTORY_SEPARATOR . static::customDirectory . $file is assigned to $langCustomFile
    in app/Language.php on line 369
  3. Path: DateTimeField::__construct() is called in app/Fields/Time.php on line 44
  1. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  2. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  3. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  4. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime, 2) is assigned to $value
    in include/fields/DateTimeField.php on line 47
  5. Data is passed through convertToDBFormat(), and self::convertToDBFormat($value[0]) is assigned to $insert_date
    in include/fields/DateTimeField.php on line 53
  6. $insert_date is returned
    in include/fields/DateTimeField.php on line 55
  7. new DateTimeField($value)->getDBInsertDateValue() is returned
    in app/Fields/Date.php on line 168
  8. App\Validator::dateInUserFormat($input) ? $convert ? App\Fields\Date::formatToDB($input) : $input : null is assigned to $value
    in app/Purifier.php on line 451
  9. $value is returned
    in app/Purifier.php on line 569
  10. App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is assigned to property Request::$purifiedValuesByType
    in app/Request.php on line 170
  11. Read from property Request::$purifiedValuesByType, and $this->purifiedValuesByType[$key][$type] is returned
    in app/Request.php on line 167
  12. Time::formatToDB() is called
    in modules/Settings/BusinessHours/actions/Save.php on line 30
  13. Enters via parameter $time
    in app/Fields/Time.php on line 41
  14. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  15. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  16. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  17. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime) is assigned to $date_value
    in include/fields/DateTimeField.php on line 299
  18. Data is passed through convertToUserFormat(), and self::convertToUserFormat($date_value) is returned
    in include/fields/DateTimeField.php on line 308
  19. $this->getDisplayDate($user) . ' ' . $this->getDisplayTime($user) is returned
    in include/fields/DateTimeField.php on line 72
  20. new DateTimeField($value)->getDisplayDateTimeValue() is returned
    in app/Fields/DateTime.php on line 35
  21. Mailer::sendFromTemplate() is called
    in modules/Users/actions/LoginForgotPassword.php on line 41
  22. Enters via parameter $params
    in app/Mailer.php on line 99
  23. TextParser::setLanguage() is called
    in app/Mailer.php on line 123
  24. Enters via parameter $name
    in app/TextParser.php on line 318
  25. $name is assigned to property TextParser::$language
    in app/TextParser.php on line 320
  26. Read from property TextParser::$language, and Language::translate() is called
    in app/TextParser.php on line 722
  27. Enters via parameter $language
    in app/Language.php on line 161
  28. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  29. Enters via parameter $language
    in app/Language.php on line 357
  30. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  31. App\ROOT_DIRECTORY . DIRECTORY_SEPARATOR . static::customDirectory . $file is assigned to $langCustomFile
    in app/Language.php on line 369
  4. Path: Request::set() is called in modules/Vtiger/models/Widget.php on line 615
  1. Request::set() is called
    in modules/Vtiger/models/Widget.php on line 615
  2. Enters via parameter $value
    in app/Request.php on line 594
  3. $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByInteger
    in app/Request.php on line 599
  4. Read from property Request::$purifiedValuesByInteger, and $this->purifiedValuesByInteger[$key] is returned
    in app/Request.php on line 203
  5. $request->getInteger('dashboardId') is assigned to $dashboardId
    in modules/Vtiger/models/Widget.php on line 608
  6. Request::set() is called
    in modules/Vtiger/models/Widget.php on line 615
  7. Enters via parameter $value
    in app/Request.php on line 594
  8. $this->purifiedValuesByInteger[$key] = $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByGet
    in app/Request.php on line 599
  9. Read from property Request::$purifiedValuesByGet, and $this->purifiedValuesByGet[$key] is returned
    in app/Request.php on line 129
  10. Users_Module_Model::checkUserName() is called
    in modules/Users/actions/Save.php on line 56
  11. Enters via parameter $userName
    in modules/Users/models/Module.php on line 193
  12. Language::translate() is called
    in app/Language.php on line 214
  13. Enters via parameter $language
    in app/Language.php on line 161
  14. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  15. Enters via parameter $language
    in app/Language.php on line 357
  16. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  17. App\ROOT_DIRECTORY . DIRECTORY_SEPARATOR . static::customDirectory . $file is assigned to $langCustomFile
    in app/Language.php on line 369
  5. Path: Read tainted data from array, and Data is passed through explode(), and explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) is assigned to $code in install/views/Index.php on line 74
  1. Read tainted data from array, and Data is passed through explode(), and explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) is assigned to $code
    in install/views/Index.php on line 74
  2. $code is assigned to $lang
    in install/views/Index.php on line 79
  3. Request::set() is called
    in install/views/Index.php on line 87
  4. Enters via parameter $value
    in app/Request.php on line 594
  5. $this->purifiedValuesByInteger[$key] = $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByGet
    in app/Request.php on line 599
  6. Read from property Request::$purifiedValuesByGet, and $this->purifiedValuesByGet[$key] is returned
    in app/Request.php on line 129
  7. Users_Module_Model::checkUserName() is called
    in modules/Users/actions/Save.php on line 56
  8. Enters via parameter $userName
    in modules/Users/models/Module.php on line 193
  9. Language::translate() is called
    in app/Language.php on line 214
  10. Enters via parameter $language
    in app/Language.php on line 161
  11. Language::loadLanguageFile() is called
    in app/Language.php on line 178
  12. Enters via parameter $language
    in app/Language.php on line 357
  13. DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $moduleName . '.' . static::FORMAT is assigned to $file
    in app/Language.php on line 364
  14. App\ROOT_DIRECTORY . DIRECTORY_SEPARATOR . static::customDirectory . $file is assigned to $langCustomFile
    in app/Language.php on line 369

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
372
					foreach ($translation as $type => $rows) {
373
						foreach ($rows as $key => $val) {
374
							static::$languageContainer[$language][$moduleName][$type][$key] = $val;
375
						}
376
					}
377
				}
378
				if (!file_exists($langFile) && !file_exists($langCustomFile)) {
379
					\App\Log::info("Language file does not exist, module: $moduleName ,language: $language");
380 1
				}
381
				Cache::save('LanguageFiles', $language . $moduleName, static::$languageContainer[$language][$moduleName], Cache::LONG);
382 1
			}
383 1
		}
384 1
	}
385 1
386 1
	/**
387 1
	 * Get language from file.
388
	 *
389 1
	 * @param string $moduleName
390 1
	 * @param string $language
391 1
	 *
392 1
	 * @return array
393 1
	 */
394
	public static function getFromFile($moduleName, $language)
395
	{
396 1
		static::loadLanguageFile($language, $moduleName);
397 1
		if (isset(static::$languageContainer[$language][$moduleName])) {
398 1
			return static::$languageContainer[$language][$moduleName];
399
		}
400 1
	}
401
402
	/**
403
	 * Functions that gets translated string.
404
	 *
405
	 * @param string $moduleName
406
	 *
407
	 * @return string[]
408
	 */
409
	public static function getJsStrings($moduleName)
410
	{
411
		$language = static::getLanguage();
412
		$moduleName = str_replace([':', '.'], [\DIRECTORY_SEPARATOR, \DIRECTORY_SEPARATOR], $moduleName);
413
		static::loadLanguageFile($language, $moduleName);
414
		$return = [];
415
		if (isset(static::$languageContainer[$language][$moduleName]['js'])) {
416
			$return = static::$languageContainer[$language][$moduleName]['js'];
417 2
		}
418
		if (0 === strpos($moduleName, 'Settings')) {
419
			$base = 'Settings' . \DIRECTORY_SEPARATOR . '_Base';
420 2
			static::loadLanguageFile($language, $base);
421
			if (isset(static::$languageContainer[$language][$base]['js'])) {
422
				$return = array_merge(static::$languageContainer[$language][$base]['js'], $return);
423 2
			}
424
		}
425
		static::loadLanguageFile($language);
426 2
		if (isset(static::$languageContainer[$language]['_Base']['js'])) {
427
			$return = array_merge(static::$languageContainer[$language]['_Base']['js'], $return);
428
		}
429
		return $return;
430 2
	}
431
432
	/**
433 2
	 * This function returns the modified keycode to match the plural form(s) of a given language and a given count with the same pattern used by i18next JS library
434
	 * Global patterns for keycode are as below :
435
	 * - No plural form : only one non modified key is needed :)
436
	 * - 2 forms : unmodified key for singular values and 'key_PLURAL' for plural values
437
	 * - 3 or more forms : key_X with X indented for each plural form.
438
	 *
439
	 * @see https://www.i18next.com/plurals.html for some examples
440
	 * @see https://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms for whole plural rules used by getText
441 2
	 *
442 2
	 * @param float $count Quantityu for plural determination
443
	 *
444 2
	 * @return string Pluralized key to look for
445 2
	 */
446 2
	private static function getPluralized($count)
447 2
	{
448 2
		//Extract language code from locale with special cases
449 2
		if (0 === strcasecmp(static::getLanguage(), 'pt-BR')) {
450
			$lang = 'pt-BR';
451
		} else {
452
			$lang = static::getShortLanguageName();
453
		}
454
		//No plural form
455
		if (\in_array($lang, ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'km', 'ko', 'lo', 'ms', 'my', 'sah', 'su', 'th', 'tt', 'ug', 'vi', 'wo', 'zh'])) {
456
			return '_0';
457
		}
458
		//Two plural forms
459
		if (\in_array($lang, ['ach', 'ak', 'am', 'arn', 'br', 'fa', 'fil', 'fr', 'gun', 'ln', 'mfe', 'mg', 'mi', 'oc', 'pt-BR', 'tg', 'ti', 'tr', 'uz', 'wa'])) {
460 2
			return ($count > 1) ? '_1' : '_0';
461 2
		}
462
		if (\in_array($lang, [
463
			'af', 'an', 'anp', 'as', 'ast', 'az', 'bg', 'bn', 'brx', 'ca', 'da', 'de', 'doi', 'dz', 'el', 'en', 'eo', 'es', 'et', 'eu', 'ff', 'fi', 'fo', 'fur', 'fy',
464
			'gl', 'gu', 'ha', 'he', 'hi', 'hne', 'hu', 'hy', 'ia', 'it', 'kk', 'kl', 'kn', 'ku', 'ky', 'lb', 'mai', 'mk', 'ml', 'mn', 'mni', 'mr', 'nah', 'nap',
465
			'nb', 'ne', 'nl', 'nn', 'nso', 'or', 'pa', 'pap', 'pms', 'ps', 'pt', 'rm', 'rw', 'sat', 'sco', 'sd', 'se', 'si', 'so', 'son', 'sq', 'sv', 'sw',
466
			'ta', 'te', 'tk', 'ur', 'yo',
467
		])) {
468
			return (1 !== $count) ? '_1' : '_0';
0 ignored issues
show
introduced by
The condition 1 !== $count is always true.
Loading history...
469
		}
470 2
		switch ($lang) {
471
			case 'is':
472
				return (1 !== $count % 10 || 11 === $count % 100) ? '_1' : '_0';
473
			case 'be':
474
			case 'bs':
475
			case 'hr':
476
			case 'ru':
477
			case 'sr':
478
			case 'uk':
479
				$i = $count % 10;
480
				$j = $count % 100;
481 2
				if (1 === $i && 11 !== $j) {
482
					return '_0';
483
				}
484
				if ($i >= 2 && $i <= 4 && ($j < 10 || $j >= 20)) {
485
					return '_1';
486
				}
487
488
				return '_2';
489
			case 'cs':
490
			case 'sk':
491
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
492 2
					return '_0';
493
				}
494
				if ($count >= 2 && $count <= 4) {
495
					return '_1';
496
				}
497
498
				return '_2';
499
			case 'csb':
500
				$i = $count % 10;
501
				$j = $count % 100;
502
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
503 2
					return '_0';
504
				}
505
				if ($i >= 2 && $i <= 4 && ($j < 10 || $j >= 20)) {
506
					return '_1';
507
				}
508
509
				return '_2';
510
			case 'lt':
511
				$i = $count % 10;
512
				$j = $count % 100;
513
				if (1 == $i && 11 != $j) {
514 2
					return '_0';
515 2
				}
516 2
				if ($i >= 2 && ($j < 10 || $j >= 20)) {
517 2
					return '_1';
518 2
				}
519
520 2
				return '_2';
521 2
			case 'lv':
522
				$i = $count % 10;
523
				$j = $count % 100;
524 2
				if (1 == $i && 11 != $j) {
525
					return '_0';
526
				}
527
				if (0 !== $count) {
0 ignored issues
show
introduced by
The condition 0 !== $count is always true.
Loading history...
528
					return '_1';
529
				}
530
531
				return '_2';
532
			case 'me':
533
				$i = $count % 10;
534
				$j = $count % 100;
535
				if (1 === $i && 11 !== $j) {
536
					return '_0';
537
				}
538
				if ($i >= 2 && $i <= 4 && ($j < 10 || $j >= 20)) {
539
					return '_1';
540
				}
541
542
				return '_2';
543
			case 'pl':
544
				$i = $count % 10;
545
				$j = $count % 100;
546
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
547
					return '_0';
548
				}
549
				if ($i >= 2 && $i <= 4 && ($j < 10 || $j >= 20)) {
550
					return '_1';
551
				}
552
553
				return '_2';
554
			case 'ro':
555
				$j = $count % 100;
556
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
557
					return '_0';
558
				}
559
				if (0 === $count || ($j > 0 && $j < 20)) {
560
					return '_1';
561
				}
562
563
				return '_2';
564
			case 'cy':
565
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
566
					return '_0';
567
				}
568
				if (2 === $count) {
0 ignored issues
show
introduced by
The condition 2 === $count is always false.
Loading history...
569
					return '_1';
570
				}
571
				if (8 !== $count && 11 !== $count) {
0 ignored issues
show
introduced by
The condition 11 !== $count is always true.
Loading history...
572
					return '_2';
573
				}
574
575
				return '_3';
576
			case 'gd':
577
				if (1 === $count || 11 === $count) {
0 ignored issues
show
introduced by
The condition 11 === $count is always false.
Loading history...
578
					return '_0';
579
				}
580
				if (2 === $count || 12 === $count) {
0 ignored issues
show
introduced by
The condition 12 === $count is always false.
Loading history...
581
					return '_1';
582
				}
583
				if ($count > 2 && $count < 20) {
584
					return '_2';
585
				}
586
587
				return '_3';
588
			case 'kw':
589
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
590
					return '_0';
591
				}
592
				if (2 === $count) {
0 ignored issues
show
introduced by
The condition 2 === $count is always false.
Loading history...
593
					return '_1';
594
				}
595
				if (3 === $count) {
0 ignored issues
show
introduced by
The condition 3 === $count is always false.
Loading history...
596
					return '_2';
597
				}
598
599
				return '_3';
600
			case 'mt':
601
				$j = $count % 100;
602
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
603
					return '_0';
604
				}
605
				if (0 === $count || ($j > 1 && $j < 11)) {
606
					return '_1';
607
				}
608
				if ($j > 10 && $j < 20) {
609
					return '_2';
610
				}
611
612
				return '_3';
613
			case 'sl':
614
				$j = $count % 100;
615
				if (1 === $j) {
616
					return '_0';
617
				}
618
				if (2 === $j) {
619
					return '_1';
620
				}
621
				if (3 === $j || 4 === $j) {
622
					return '_2';
623
				}
624
625
				return '_3';
626
			case 'ga':
627
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
628
					return '_0';
629
				}
630
				if (2 === $count) {
0 ignored issues
show
introduced by
The condition 2 === $count is always false.
Loading history...
631
					return '_1';
632
				}
633
				if ($count > 2 && $count < 7) {
634
					return '_2';
635
				}
636
				if ($count > 6 && $count < 11) {
637
					return '_3';
638
				}
639
640
				return '_4';
641
			case 'ar':
642 2
				if (0 === $count) {
0 ignored issues
show
introduced by
The condition 0 === $count is always false.
Loading history...
643
					return '_0';
644 2
				}
645
				if (1 === $count) {
0 ignored issues
show
introduced by
The condition 1 === $count is always false.
Loading history...
646
					return '_1';
647
				}
648
				if (2 === $count) {
0 ignored issues
show
introduced by
The condition 2 === $count is always false.
Loading history...
649
					return '_2';
650
				}
651
				if ($count % 100 >= 3 && $count % 100 <= 10) {
652
					return '_3';
653
				}
654
				if ($count * 100 >= 11) {
655 5
					return '_4';
656
				}
657 5
658 5
				return '_5';
659 2
			default:
660 1
				return '';
661
		}
662 1
	}
663
664 5
	/**
665 5
	 * Function to get the label name of the Langauge package.
666 5
	 *
667 5
	 * @param string $prefix
668 5
	 *
669 5
	 * @return bool|string
670 5
	 */
671
	public static function getLanguageLabel(string $prefix)
672 5
	{
673
		return static::getLangInfo($prefix)['name'] ?? null;
674 5
	}
675 5
676 5
	/**
677 5
	 * Function return languages data.
678 4
	 *
679
	 * @param bool $active
680 1
	 * @param bool $allData
681
	 *
682
	 * @return array
683
	 */
684
	public static function getAll(bool $active = true, bool $allData = false)
685
	{
686
		$cacheKey = $active ? 'Active' : 'All';
687
		if (Cache::has('getAllLanguages', $cacheKey)) {
688
			if (!$allData) {
689
				return array_column(Cache::get('getAllLanguages', $cacheKey), 'name', 'prefix');
690 5
			}
691
			return Cache::get('getAllLanguages', $cacheKey);
692 5
		}
693 2
		$all = [];
694
		$actives = [];
695 3
		$dataReader = (new Db\Query())->from('vtiger_language')->createCommand()->query();
696
		while ($row = $dataReader->read()) {
697
			$all[$row['prefix']] = $row;
698
			if (1 === (int) $row['active']) {
699
				$actives[$row['prefix']] = $row;
700
			}
701
			Cache::save('getLangInfo', $row['prefix'], $row);
702
		}
703
		$dataReader->close();
704
		Cache::save('getAllLanguages', 'All', $all);
705
		Cache::save('getAllLanguages', 'Active', $actives);
706
		if (!$allData) {
707
			return array_column(Cache::get('getAllLanguages', $cacheKey), 'name', 'prefix');
708
		}
709
		return Cache::get('getAllLanguages', $cacheKey);
710 1
	}
711
712 1
	/**
713 1
	 * Function return languange data.
714 1
	 *
715 1
	 * @param string $prefix
716 1
	 *
717
	 * @return array
718 1
	 */
719 1
	public static function getLangInfo(string $prefix)
720 1
	{
721 1
		if (Cache::has('getLangInfo', $prefix)) {
722 1
			return Cache::get('getLangInfo', $prefix);
723
		}
724
		return Cache::save('getLangInfo', $prefix, (new Db\Query())->from('vtiger_language')->where(['prefix' => $prefix])->one());
0 ignored issues
show
Bug Best Practice introduced by
The expression return App\Cache::save('...x' => $prefix))->one()) returns the type boolean which is incompatible with the documented return type array.
Loading history...
725
	}
726
727 1
	/**
728 1
	 * Translation modification.
729 1
	 *
730
	 * @param string $language
731 1
	 * @param string $fileName
732
	 * @param string $type
733
	 * @param string $label
734 1
	 * @param string $translation
735 1
	 * @param bool   $remove
736
	 *
737
	 * @throws Exceptions\AppException
738
	 */
739
	public static function translationModify(string $language, string $fileName, string $type, string $label, string $translation, bool $remove = false)
740 1
	{
741
		$fileLocation = explode('__', $fileName, 2);
742 1
		array_unshift($fileLocation, 'custom', 'languages', $language);
743 1
		$fileDirectory = ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . implode(\DIRECTORY_SEPARATOR, $fileLocation) . '.' . static::FORMAT;
744 1
		if (file_exists($fileDirectory)) {
745 1
			$translations = Json::decode(file_get_contents($fileDirectory), true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type integer expected by parameter $objectDecodeType of App\Json::decode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

745
			$translations = Json::decode(file_get_contents($fileDirectory), /** @scrutinizer ignore-type */ true);
Loading history...
746 1
		} else {
747 1
			$loc = '';
748 1
			array_pop($fileLocation);
749 1
			foreach ($fileLocation as $name) {
750
				$loc .= \DIRECTORY_SEPARATOR . $name;
751 1
				if (!file_exists(ROOT_DIRECTORY . $loc) && !mkdir(ROOT_DIRECTORY . $loc, 0755)) {
752 1
					throw new Exceptions\AppException('ERR_NO_PERMISSIONS_TO_CREATE_DIRECTORIES');
753 1
				}
754
			}
755
		}
756
		$translations[$type][$label] = $translation;
757
		if ($remove) {
758 1
			unset($translations[$type][$label]);
759 1
		}
760
		if (false === Json::save($fileDirectory, $translations)) {
761
			throw new Exceptions\AppException('ERR_CREATE_FILE_FAILURE');
762 1
		}
763
		Cache::delete('LanguageFiles', $language . str_replace('__', \DIRECTORY_SEPARATOR, $fileName));
764
	}
765
766
	/**
767
	 * Set locale information.
768
	 */
769
	public static function initLocale()
770
	{
771
		$original = explode(';', setlocale(LC_ALL, 0));
772
		$defaultCharset = strtolower(\App\Config::main('default_charset'));
773
		setlocale(
774
			LC_ALL,
775
			\Locale::acceptFromHttp(self::getLanguage()) . '.' . $defaultCharset,
776
			\Locale::acceptFromHttp(\App\Config::main('default_language')) . '.' . $defaultCharset,
777
			\Locale::acceptFromHttp(self::DEFAULT_LANG) . ".$defaultCharset",
778
			\Locale::acceptFromHttp(self::DEFAULT_LANG) . '.utf8'
779
		);
780
		foreach ($original as $localeSetting) {
781
			if (false !== strpos($localeSetting, '=')) {
782
				[$category, $locale] = explode('=', $localeSetting);
783
			} else {
784
				$category = 'LC_ALL';
785
				$locale = $localeSetting;
786
			}
787
			if ('LC_COLLATE' !== $category && 'LC_CTYPE' !== $category && \defined($category)) {
788
				setlocale(\constant($category), $locale);
789
			}
790
		}
791
	}
792
793
	/**
794
	 * Get display language name.
795
	 *
796
	 * @param string $prefix
797
	 *
798
	 * @return string
799
	 */
800
	public static function getDisplayName(string $prefix)
801
	{
802
		return Utils::mbUcfirst(locale_get_region($prefix) === strtoupper(locale_get_primary_language($prefix)) ? locale_get_display_language($prefix, $prefix) : locale_get_display_name($prefix, $prefix));
803
	}
804
805
	/**
806
	 * Get region from language prefix.
807
	 *
808
	 * @param string $prefix
809
	 *
810
	 * @return mixed
811
	 */
812
	public static function getRegion(string $prefix)
813
	{
814
		return locale_parse($prefix)['region'];
815
	}
816
}
817