Issues (1401)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/includes/LanguageFallbackChainFactory.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Wikibase\Lib;
4
5
use Babel;
6
use ExtensionRegistry;
7
use IContextSource;
8
use InvalidArgumentException;
9
use Language;
10
use LanguageConverter;
11
use MediaWiki\Languages\LanguageConverterFactory;
12
use MediaWiki\Languages\LanguageFactory;
13
use MediaWiki\Languages\LanguageFallback;
14
use MediaWiki\MediaWikiServices;
15
use MWException;
16
use User;
17
18
/**
19
 * Object creating TermLanguageFallbackChain objects in Wikibase.
20
 *
21
 * @license GPL-2.0-or-later
22
 * @author Liangent < [email protected] >
23
 */
24
class LanguageFallbackChainFactory {
25
26
	/**
27
	 * Fallback levels
28
	 */
29
	const FALLBACK_ALL = 0xff;
30
31
	/**
32
	 * The language itself, e.g. 'en' for 'en'.
33
	 */
34
	const FALLBACK_SELF = 1;
35
36
	/**
37
	 * Other compatible languages that can be translated into the requested language
38
	 * (and translation is automatically done), e.g. 'sr', 'sr-ec' and 'sr-el' for 'sr'.
39
	 */
40
	const FALLBACK_VARIANTS = 2;
41
42
	/**
43
	 * All other language from the system fallback chain, e.g. 'de' and 'en' for 'de-formal'.
44
	 */
45
	const FALLBACK_OTHERS = 4;
46
47
	/** @var LanguageFactory */
48
	private $languageFactory;
49
50
	/** @var LanguageConverterFactory */
51
	private $languageConverterFactory;
52
53
	/** @var LanguageFallback */
54
	private $languageFallback;
55
56
	/**
57
	 * @var array[]
58
	 */
59
	private $languageCache = [];
60
61
	/**
62
	 * @var array[]
63
	 */
64
	private $userLanguageCache = [];
65
66
	public function __construct(
67
		?LanguageFactory $languageFactory = null,
68
		?LanguageConverterFactory $languageConverterFactory = null,
69
		?LanguageFallback $languageFallback = null
70
	) {
71
		$services = MediaWikiServices::getInstance();
72
		$this->languageFactory = $languageFactory ?: $services->getLanguageFactory();
73
		$this->languageConverterFactory = $languageConverterFactory ?: $services->getLanguageConverterFactory();
74
		$this->languageFallback = $languageFallback ?: $services->getLanguageFallback();
75
	}
76
77
	/**
78
	 * Get the fallback chain based a single language, and specified fallback level.
79
	 *
80
	 * @param Language $language
81
	 * @param int $mode Bitfield of self::FALLBACK_*
82
	 *
83
	 * @return TermLanguageFallbackChain
84
	 */
85
	public function newFromLanguage( Language $language, $mode = self::FALLBACK_ALL ) {
86
		$languageCode = $language->getCode();
87
88
		if ( !isset( $this->languageCache[$languageCode][$mode] ) ) {
89
			$chain = $this->buildFromLanguage( $language, $mode );
90
			$this->languageCache[$languageCode][$mode] = new TermLanguageFallbackChain(
91
				$chain,
0 ignored issues
show
$chain is of type array<integer,object<Wik...anguageWithConversion>>, but the function expects a array<integer,object<Wik...anguageWithConversion>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
92
				WikibaseContentLanguages::getDefaultInstance()->getContentLanguages( WikibaseContentLanguages::CONTEXT_TERM )
93
			);
94
		}
95
96
		return $this->languageCache[$languageCode][$mode];
97
	}
98
99
	/**
100
	 * Get the fallback chain based a single language code, and specified fallback level.
101
	 *
102
	 * @param string $languageCode
103
	 * @param int $mode Bitfield of self::FALLBACK_*
104
	 *
105
	 * @return TermLanguageFallbackChain
106
	 */
107
	public function newFromLanguageCode( $languageCode, $mode = self::FALLBACK_ALL ) {
108
		$languageCode = LanguageWithConversion::validateLanguageCode( $languageCode );
109
110
		if ( !isset( $this->languageCache[$languageCode][$mode] ) ) {
111
			$chain = $this->buildFromLanguage( $languageCode, $mode );
112
			$this->languageCache[$languageCode][$mode] = new TermLanguageFallbackChain(
113
				$chain,
0 ignored issues
show
$chain is of type array<integer,object<Wik...anguageWithConversion>>, but the function expects a array<integer,object<Wik...anguageWithConversion>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
114
				WikibaseContentLanguages::getDefaultInstance()->getContentLanguages( WikibaseContentLanguages::CONTEXT_TERM )
115
			);
116
		}
117
118
		return $this->languageCache[$languageCode][$mode];
119
	}
120
121
	/**
122
	 * Build fallback chain array for a given language or validated language code.
123
	 *
124
	 * @param Language|string $language Language object or language code as string
125
	 * @param int $mode Bitfield of self::FALLBACK_*
126
	 * @param TermLanguageFallbackChain[] $chain for recursive calls
127
	 * @param bool[] $fetched for recursive calls
128
	 *
129
	 * @throws InvalidArgumentException
130
	 * @return LanguageWithConversion[]
131
	 */
132
	private function buildFromLanguage( $language, $mode, array $chain = [], array &$fetched = [] ) {
133
		if ( !is_int( $mode ) ) {
134
			throw new InvalidArgumentException( '$mode must be an integer' );
135
		}
136
137
		if ( is_string( $language ) ) {
138
			$languageCode = $language;
139
		} else {
140
			$languageCode = $language->getCode();
141
		}
142
143
		if ( $mode & self::FALLBACK_SELF ) {
144
			if ( !isset( $fetched[$languageCode] ) ) {
145
				$chain[] = LanguageWithConversion::factory( $language );
146
				$fetched[$languageCode] = true;
147
			}
148
		}
149
150
		if ( $mode & self::FALLBACK_VARIANTS ) {
151
			$parentLanguage = null;
152
			$pieces = explode( '-', $languageCode, 2 );
153
154
			if ( in_array( $pieces[0], LanguageConverter::$languagesWithVariants ) ) {
155
				if ( is_string( $language ) ) {
156
					$language = $this->languageFactory->getLanguage( $language );
157
				}
158
				$parentLanguage = $this->languageFactory->getParentLanguage( $language->getCode() );
159
			}
160
161
			if ( $parentLanguage ) {
162
				// It's less likely to trigger conversion mistakes by converting
163
				// zh-tw to zh-hk first instead of converting zh-cn to zh-tw.
164
				$parentLanguageConverter = $this->languageConverterFactory->getLanguageConverter( $parentLanguage );
165
				$variantFallbacks = $parentLanguageConverter->getVariantFallbacks( $languageCode );
166
				if ( is_array( $variantFallbacks ) ) {
167
					$variants = array_unique( array_merge(
168
						$variantFallbacks,
169
						$parentLanguageConverter->getVariants()
170
					) );
171
				} else {
172
					$variants = $parentLanguageConverter->getVariants();
173
				}
174
175
				foreach ( $variants as $variant ) {
176
					if ( !isset( $fetched[$variant] )
177
						// The self::FALLBACK_SELF mode is already responsible for self-references.
178
						&& $variant !== $languageCode
179
						&& $parentLanguageConverter->hasVariant( $variant )
180
					) {
181
						$chain[] = LanguageWithConversion::factory( $language, $variant );
182
						$fetched[$variant] = true;
183
					}
184
				}
185
			}
186
		}
187
188
		if ( $mode & self::FALLBACK_OTHERS ) {
189
			// Regarding $mode in recursive calls:
190
			// * self is a must to have the fallback item itself included;
191
			// * respect the original caller about whether to include variants or not;
192
			// * others should be excluded as they'll be handled here in loops.
193
			$recursiveMode = $mode;
194
			$recursiveMode &= self::FALLBACK_VARIANTS;
195
			$recursiveMode |= self::FALLBACK_SELF;
196
197
			$fallbacks = $this->languageFallback->getAll( $languageCode );
198
			foreach ( $fallbacks as $other ) {
199
				$chain = $this->buildFromLanguage( $other, $recursiveMode, $chain, $fetched );
0 ignored issues
show
$chain is of type array<integer,object<Wik...anguageWithConversion>>, but the function expects a array<integer,object<Wik...LanguageFallbackChain>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
200
			}
201
		}
202
203
		return $chain;
204
	}
205
206
	/**
207
	 * Construct the fallback chain based on a context. Currently it just uses user and language info in it.
208
	 *
209
	 * @param IContextSource $context
210
	 *
211
	 * @return TermLanguageFallbackChain
212
	 */
213
	public function newFromContext( IContextSource $context ) {
214
		return $this->newFromUserAndLanguageCode( $context->getUser(), $context->getLanguage()->getCode() );
215
	}
216
217
	/**
218
	 * Construct the fallback chain based on a context, but ignore the language info in it and use a specified one instead.
219
	 *
220
	 * @param IContextSource $context
221
	 * @param string $languageCode
222
	 *
223
	 * @return TermLanguageFallbackChain
224
	 */
225
	public function newFromContextAndLanguageCode( IContextSource $context, $languageCode ) {
226
		return $this->newFromUserAndLanguageCode( $context->getUser(), $languageCode );
227
	}
228
229
	/**
230
	 * Construct the fallback chain based on a user and a language, currently from data provided by Extension:Babel.
231
	 *
232
	 * @param User $user
233
	 * @param string $languageCode
234
	 *
235
	 * @return TermLanguageFallbackChain
236
	 */
237
	public function newFromUserAndLanguageCode( User $user, $languageCode ) {
238
		if ( !ExtensionRegistry::getInstance()->isLoaded( 'Babel' ) || $user->isAnon() ) {
239
			return $this->newFromLanguageCode( $languageCode, self::FALLBACK_ALL );
240
		}
241
242
		$languageCode = LanguageWithConversion::validateLanguageCode( $languageCode );
243
244
		if ( isset( $this->userLanguageCache[$user->getName()][$languageCode] ) ) {
245
			return $this->userLanguageCache[$user->getName()][$languageCode];
246
		}
247
248
		$babel = $this->getBabel( $languageCode, $user );
249
250
		$chain = $this->buildFromBabel( $babel );
251
		$languageFallbackChain = new TermLanguageFallbackChain(
252
			$chain,
253
			WikibaseContentLanguages::getDefaultInstance()->getContentLanguages( WikibaseContentLanguages::CONTEXT_TERM )
254
		);
255
256
		$this->userLanguageCache[$user->getName()][$languageCode] = $languageFallbackChain;
257
258
		return $languageFallbackChain;
259
	}
260
261
	private function getBabel( $languageCode, $user ) {
262
		$babel = [];
263
264
		$babelCategoryNames = $this->getBabelCategoryNames();
265
266
		if ( count( $babelCategoryNames ) ) {
267
			// A little redundant but it's the only way to get required information with current Babel API.
268
			$previousLevelBabel = [];
269
270
			foreach ( $babelCategoryNames as $level => $_ ) {
271
				// Make the current language at the top of the chain.
272
				$levelBabel = array_unique( array_merge(
273
					[ $languageCode ],
274
					Babel::getCachedUserLanguages( $user, $level )
275
				) );
276
277
				$babel[$level] = array_diff( $levelBabel, $previousLevelBabel );
278
				$previousLevelBabel = $levelBabel;
279
			}
280
		} else {
281
			$babel['N'] = [ $languageCode ];
282
		}
283
284
		return $babel;
285
	}
286
287
	private function getBabelCategoryNames() {
288
		global $wgBabelCategoryNames;
289
290
		$babelCategoryNames = array_filter(
291
			$wgBabelCategoryNames,
292
			function( $category ) {
293
				return $category !== false;
294
			}
295
		);
296
297
		krsort( $babelCategoryNames );
298
299
		return $babelCategoryNames;
300
	}
301
302
	/**
303
	 * Build fallback chain array for a given babel array.
304
	 *
305
	 * @param array $babel
306
	 *
307
	 * @return LanguageWithConversion[]
308
	 */
309
	public function buildFromBabel( array $babel ) {
310
		$chain = [];
311
		$fetched = [];
312
313
		// First pass to get "compatible" languages (self and variants)
314
		foreach ( $babel as $languageCodes ) { // Already sorted when added
315
			foreach ( [ self::FALLBACK_SELF, self::FALLBACK_VARIANTS ] as $mode ) {
316
				foreach ( $languageCodes as $languageCode ) {
317
					try {
318
						$languageCode = LanguageWithConversion::validateLanguageCode( $languageCode );
319
					} catch ( MWException $e ) {
0 ignored issues
show
The class MWException does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
320
						continue;
321
					}
322
					$chain = $this->buildFromLanguage( $languageCode, $mode, $chain, $fetched );
323
				}
324
			}
325
		}
326
327
		// Second pass to get other languages from system fallback chain
328
		foreach ( $babel as $languageCodes ) {
329
			foreach ( $languageCodes as $languageCode ) {
330
				try {
331
					$languageCode = LanguageWithConversion::validateLanguageCode( $languageCode );
332
				} catch ( MWException $e ) {
0 ignored issues
show
The class MWException does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
333
					continue;
334
				}
335
				$chain = $this->buildFromLanguage(
336
					$languageCode,
337
					self::FALLBACK_OTHERS | self::FALLBACK_VARIANTS,
338
					$chain,
339
					$fetched
340
				);
341
			}
342
		}
343
344
		return $chain;
345
	}
346
347
}
348