Issues (4122)

Security Analysis    not enabled

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.

resourceloader/ResourceLoaderClientHtml.php (13 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
 * This program is free software; you can redistribute it and/or modify
4
 * it under the terms of the GNU General Public License as published by
5
 * the Free Software Foundation; either version 2 of the License, or
6
 * (at your option) any later version.
7
 *
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
 * GNU General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU General Public License along
14
 * with this program; if not, write to the Free Software Foundation, Inc.,
15
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
 * http://www.gnu.org/copyleft/gpl.html
17
 *
18
 * @file
19
 */
20
21
use WrappedString\WrappedStringList;
22
23
/**
24
 * Bootstrap a ResourceLoader client on an HTML page.
25
 *
26
 * @since 1.28
27
 */
28
class ResourceLoaderClientHtml {
29
30
	/** @var ResourceLoaderContext */
31
	private $context;
32
33
	/** @var ResourceLoader */
34
	private $resourceLoader;
35
36
	/** @var string|null */
37
	private $target;
38
39
	/** @var array */
40
	private $config = [];
41
42
	/** @var array */
43
	private $modules = [];
44
45
	/** @var array */
46
	private $moduleStyles = [];
47
48
	/** @var array */
49
	private $moduleScripts = [];
50
51
	/** @var array */
52
	private $exemptStates = [];
53
54
	/** @var array */
55
	private $data;
56
57
	/**
58
	 * @param ResourceLoaderContext $context
59
	 * @param aray $target [optional] Custom 'target' parameter for the startup module
60
	 */
61
	public function __construct( ResourceLoaderContext $context, $target = null ) {
62
		$this->context = $context;
63
		$this->resourceLoader = $context->getResourceLoader();
64
		$this->target = $target;
0 ignored issues
show
Documentation Bug introduced by
It seems like $target can also be of type object<aray>. However, the property $target is declared as type string|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
65
	}
66
67
	/**
68
	 * Set mw.config variables.
69
	 *
70
	 * @param array $vars Array of key/value pairs
71
	 */
72
	public function setConfig( array $vars ) {
73
		foreach ( $vars as $key => $value ) {
74
			$this->config[$key] = $value;
75
		}
76
	}
77
78
	/**
79
	 * Ensure one or more modules are loaded.
80
	 *
81
	 * @param array $modules Array of module names
82
	 */
83
	public function setModules( array $modules ) {
84
		$this->modules = $modules;
85
	}
86
87
	/**
88
	 * Ensure the styles of one or more modules are loaded.
89
	 *
90
	 * @deprecated since 1.28
91
	 * @param array $modules Array of module names
92
	 */
93
	public function setModuleStyles( array $modules ) {
94
		$this->moduleStyles = $modules;
95
	}
96
97
	/**
98
	 * Ensure the scripts of one or more modules are loaded.
99
	 *
100
	 * @deprecated since 1.28
101
	 * @param array $modules Array of module names
102
	 */
103
	public function setModuleScripts( array $modules ) {
104
		$this->moduleScripts = $modules;
105
	}
106
107
	/**
108
	 * Set state of special modules that are handled by the caller manually.
109
	 *
110
	 * See OutputPage::buildExemptModules() for use cases.
111
	 *
112
	 * @param array $modules Module state keyed by module name
0 ignored issues
show
There is no parameter named $modules. 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 $italy is not defined by the method finale(...).

/**
 * @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...
113
	 */
114
	public function setExemptStates( array $states ) {
115
		$this->exemptStates = $states;
116
	}
117
118
	/**
119
	 * @return array
120
	 */
121
	private function getData() {
122
		if ( $this->data ) {
123
			// @codeCoverageIgnoreStart
124
			return $this->data;
125
			// @codeCoverageIgnoreEnd
126
		}
127
128
		$rl = $this->resourceLoader;
129
		$data = [
130
			'states' => [
131
				// moduleName => state
132
			],
133
			'general' => [
134
				// position => [ moduleName ]
135
				'top' => [],
136
				'bottom' => [],
137
			],
138
			'styles' => [
139
				// moduleName
140
			],
141
			'scripts' => [
142
				// position => [ moduleName ]
143
				'top' => [],
144
				'bottom' => [],
145
			],
146
			// Embedding for private modules
147
			'embed' => [
148
				'styles' => [],
149
				'general' => [
150
					'top' => [],
151
					'bottom' => [],
152
				],
153
			],
154
155
		];
156
157
		foreach ( $this->modules as $name ) {
158
			$module = $rl->getModule( $name );
0 ignored issues
show
Are you sure the assignment to $module is correct as $rl->getModule($name) (which targets ResourceLoader::getModule()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
159
			if ( !$module ) {
160
				continue;
161
			}
162
163
			$group = $module->getGroup();
164
			$position = $module->getPosition();
165
166 View Code Duplication
			if ( $group === 'private' ) {
167
				// Embed via mw.loader.implement per T36907.
168
				$data['embed']['general'][$position][] = $name;
169
				// Avoid duplicate request from mw.loader
170
				$data['states'][$name] = 'loading';
171
			} else {
172
				// Load via mw.loader.load()
173
				$data['general'][$position][] = $name;
174
			}
175
		}
176
177
		foreach ( $this->moduleStyles as $name ) {
178
			$module = $rl->getModule( $name );
0 ignored issues
show
Are you sure the assignment to $module is correct as $rl->getModule($name) (which targets ResourceLoader::getModule()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
179
			if ( !$module ) {
180
				continue;
181
			}
182
183
			if ( $module->getType() !== ResourceLoaderModule::LOAD_STYLES ) {
184
				$logger = $rl->getLogger();
185
				$logger->debug( 'Unexpected general module "{module}" in styles queue.', [
186
					'module' => $name,
187
				] );
188
			} else {
189
				// Stylesheet doesn't trigger mw.loader callback.
190
				// Set "ready" state to allow dependencies and avoid duplicate requests. (T87871)
191
				$data['states'][$name] = 'ready';
192
			}
193
194
			$group = $module->getGroup();
195
			$context = $this->getContext( $group, ResourceLoaderModule::TYPE_STYLES );
196
			if ( $module->isKnownEmpty( $context ) ) {
197
				// Avoid needless request for empty module
198
				$data['states'][$name] = 'ready';
199 View Code Duplication
			} else {
200
				if ( $group === 'private' ) {
201
					// Embed via style element
202
					$data['embed']['styles'][] = $name;
203
					// Avoid duplicate request from mw.loader
204
					$data['states'][$name] = 'ready';
205
				} else {
206
					// Load from load.php?only=styles via <link rel=stylesheet>
207
					$data['styles'][] = $name;
208
				}
209
			}
210
		}
211
212
		foreach ( $this->moduleScripts as $name ) {
213
			$module = $rl->getModule( $name );
0 ignored issues
show
Are you sure the assignment to $module is correct as $rl->getModule($name) (which targets ResourceLoader::getModule()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
214
			if ( !$module ) {
215
				continue;
216
			}
217
218
			$group = $module->getGroup();
219
			$position = $module->getPosition();
220
			$context = $this->getContext( $group, ResourceLoaderModule::TYPE_SCRIPTS );
221
			if ( $module->isKnownEmpty( $context ) ) {
222
				// Avoid needless request for empty module
223
				$data['states'][$name] = 'ready';
224
			} else {
225
				// Load from load.php?only=scripts via <script src></script>
226
				$data['scripts'][$position][] = $name;
227
228
				// Avoid duplicate request from mw.loader
229
				$data['states'][$name] = 'loading';
230
			}
231
		}
232
233
		return $data;
234
	}
235
236
	/**
237
	 * @return array Attribute key-value pairs for the HTML document element
238
	 */
239
	public function getDocumentAttributes() {
240
		return [ 'class' => 'client-nojs' ];
241
	}
242
243
	/**
244
	 * The order of elements in the head is as follows:
245
	 * - Inline scripts.
246
	 * - Stylesheets.
247
	 * - Async external script-src.
248
	 *
249
	 * Reasons:
250
	 * - Script execution may be blocked on preceeding stylesheets.
251
	 * - Async scripts are not blocked on stylesheets.
252
	 * - Inline scripts can't be asynchronous.
253
	 * - For styles, earlier is better.
254
	 *
255
	 * @return string|WrappedStringList HTML
256
	 */
257
	public function getHeadHtml() {
258
		$data = $this->getData();
259
		$chunks = [];
260
261
		// Change "client-nojs" class to client-js. This allows easy toggling of UI components.
262
		// This happens synchronously on every page view to avoid flashes of wrong content.
263
		// See also #getDocumentAttributes() and /resources/src/startup.js.
264
		$chunks[] = Html::inlineScript(
265
			'document.documentElement.className = document.documentElement.className'
266
			. '.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );'
267
		);
268
269
		// Inline RLQ: Set page variables
270
		if ( $this->config ) {
271
			$chunks[] = ResourceLoader::makeInlineScript(
272
				ResourceLoader::makeConfigSetScript( $this->config )
0 ignored issues
show
It seems like \ResourceLoader::makeCon...etScript($this->config) targeting ResourceLoader::makeConfigSetScript() can also be of type false; however, ResourceLoader::makeInlineScript() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
273
			);
274
		}
275
276
		// Inline RLQ: Initial module states
277
		$states = array_merge( $this->exemptStates, $data['states'] );
278
		if ( $states ) {
279
			$chunks[] = ResourceLoader::makeInlineScript(
280
				ResourceLoader::makeLoaderStateScript( $states )
0 ignored issues
show
It seems like \ResourceLoader::makeLoaderStateScript($states) targeting ResourceLoader::makeLoaderStateScript() can also be of type false; however, ResourceLoader::makeInlineScript() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
281
			);
282
		}
283
284
		// Inline RLQ: Embedded modules
285 View Code Duplication
		if ( $data['embed']['general']['top'] ) {
286
			$chunks[] = $this->getLoad(
287
				$data['embed']['general']['top'],
288
				ResourceLoaderModule::TYPE_COMBINED
289
			);
290
		}
291
292
		// Inline RLQ: Load general modules
293 View Code Duplication
		if ( $data['general']['top'] ) {
294
			$chunks[] = ResourceLoader::makeInlineScript(
295
				Xml::encodeJsCall( 'mw.loader.load', [ $data['general']['top'] ] )
0 ignored issues
show
It seems like \Xml::encodeJsCall('mw.l...ata['general']['top'])) targeting Xml::encodeJsCall() can also be of type false; however, ResourceLoader::makeInlineScript() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
296
			);
297
		}
298
299
		// Inline RLQ: Load only=scripts
300 View Code Duplication
		if ( $data['scripts']['top'] ) {
301
			$chunks[] = $this->getLoad(
302
				$data['scripts']['top'],
303
				ResourceLoaderModule::TYPE_SCRIPTS
304
			);
305
		}
306
307
		// External stylesheets
308
		if ( $data['styles'] ) {
309
			$chunks[] = $this->getLoad(
310
				$data['styles'],
311
				ResourceLoaderModule::TYPE_STYLES
312
			);
313
		}
314
315
		// Inline stylesheets (embedded only=styles)
316 View Code Duplication
		if ( $data['embed']['styles'] ) {
317
			$chunks[] = $this->getLoad(
318
				$data['embed']['styles'],
319
				ResourceLoaderModule::TYPE_STYLES
320
			);
321
		}
322
323
		// Async scripts. Once the startup is loaded, inline RLQ scripts will run.
324
		// Pass-through a custom target from OutputPage (T143066).
325
		$startupQuery = $this->target ? [ 'target' => $this->target ] : [];
326
		$chunks[] = $this->getLoad(
327
			'startup',
328
			ResourceLoaderModule::TYPE_SCRIPTS,
329
			$startupQuery
330
		);
331
332
		return WrappedStringList::join( "\n", $chunks );
333
	}
334
335
	/**
336
	 * @return string|WrappedStringList HTML
337
	 */
338
	public function getBodyHtml() {
339
		$data = $this->getData();
340
		$chunks = [];
341
342
		// Inline RLQ: Embedded modules
343 View Code Duplication
		if ( $data['embed']['general']['bottom'] ) {
344
			$chunks[] = $this->getLoad(
345
				$data['embed']['general']['bottom'],
346
				ResourceLoaderModule::TYPE_COMBINED
347
			);
348
		}
349
350
		// Inline RLQ: Load only=scripts
351 View Code Duplication
		if ( $data['scripts']['bottom'] ) {
352
			$chunks[] = $this->getLoad(
353
				$data['scripts']['bottom'],
354
				ResourceLoaderModule::TYPE_SCRIPTS
355
			);
356
		}
357
358
		// Inline RLQ: Load general modules
359 View Code Duplication
		if ( $data['general']['bottom'] ) {
360
			$chunks[] = ResourceLoader::makeInlineScript(
361
				Xml::encodeJsCall( 'mw.loader.load', [ $data['general']['bottom'] ] )
0 ignored issues
show
It seems like \Xml::encodeJsCall('mw.l...['general']['bottom'])) targeting Xml::encodeJsCall() can also be of type false; however, ResourceLoader::makeInlineScript() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
362
			);
363
		}
364
365
		return WrappedStringList::join( "\n", $chunks );
366
	}
367
368
	private function getContext( $group, $type ) {
369
		return self::makeContext( $this->context, $group, $type );
370
	}
371
372
	private function getLoad( $modules, $only, array $extraQuery = [] ) {
373
		return self::makeLoad( $this->context, (array)$modules, $only, $extraQuery );
374
	}
375
376
	private static function makeContext( ResourceLoaderContext $mainContext, $group, $type,
377
		array $extraQuery = []
378
	) {
379
		// Create new ResourceLoaderContext so that $extraQuery may trigger isRaw().
380
		$req = new FauxRequest( array_merge( $mainContext->getRequest()->getValues(), $extraQuery ) );
381
		// Set 'only' if not combined
382
		$req->setVal( 'only', $type === ResourceLoaderModule::TYPE_COMBINED ? null : $type );
383
		// Remove user parameter in most cases
384
		if ( $group !== 'user' && $group !== 'private' ) {
385
			$req->setVal( 'user', null );
386
		}
387
		$context = new ResourceLoaderContext( $mainContext->getResourceLoader(), $req );
388
		// Allow caller to setVersion() and setModules()
389
		return new DerivativeResourceLoaderContext( $context );
390
	}
391
392
	/**
393
	 * Explicily load or embed modules on a page.
394
	 *
395
	 * @param ResourceLoaderContext $mainContext
396
	 * @param array $modules One or more module names
397
	 * @param string $only ResourceLoaderModule TYPE_ class constant
398
	 * @param array $extraQuery [optional] Array with extra query parameters for the request
399
	 * @return string|WrappedStringList HTML
400
	 */
401
	public static function makeLoad( ResourceLoaderContext $mainContext, array $modules, $only,
402
		array $extraQuery = []
403
	) {
404
		$rl = $mainContext->getResourceLoader();
405
		$chunks = [];
406
407
		if ( $mainContext->getDebug() && count( $modules ) > 1 ) {
408
			$chunks = [];
409
			// Recursively call us for every item
410
			foreach ( $modules as $name ) {
411
				$chunks[] = self::makeLoad( $mainContext, [ $name ], $only, $extraQuery );
412
			}
413
			return new WrappedStringList( "\n", $chunks );
414
		}
415
416
		// Sort module names so requests are more uniform
417
		sort( $modules );
418
		// Create keyed-by-source and then keyed-by-group list of module objects from modules list
419
		$sortedModules = [];
420
		foreach ( $modules as $name ) {
421
			$module = $rl->getModule( $name );
0 ignored issues
show
Are you sure the assignment to $module is correct as $rl->getModule($name) (which targets ResourceLoader::getModule()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
422
			if ( !$module ) {
423
				$rl->getLogger()->warning( 'Unknown module "{module}"', [ 'module' => $name ] );
424
				continue;
425
			}
426
			$sortedModules[$module->getSource()][$module->getGroup()][$name] = $module;
427
		}
428
429
		foreach ( $sortedModules as $source => $groups ) {
430
			foreach ( $groups as $group => $grpModules ) {
431
				$context = self::makeContext( $mainContext, $group, $only, $extraQuery );
432
				$context->setModules( array_keys( $grpModules ) );
433
434
				if ( $group === 'private' ) {
435
					// Decide whether to use style or script element
436
					if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
437
						$chunks[] = Html::inlineStyle(
438
							$rl->makeModuleResponse( $context, $grpModules )
439
						);
440
					} else {
441
						$chunks[] = ResourceLoader::makeInlineScript(
442
							$rl->makeModuleResponse( $context, $grpModules )
443
						);
444
					}
445
					continue;
446
				}
447
448
				// See if we have one or more raw modules
449
				$isRaw = false;
450
				foreach ( $grpModules as $key => $module ) {
451
					$isRaw |= $module->isRaw();
452
				}
453
454
				// Special handling for the user group; because users might change their stuff
455
				// on-wiki like user pages, or user preferences; we need to find the highest
456
				// timestamp of these user-changeable modules so we can ensure cache misses on change
457
				// This should NOT be done for the site group (bug 27564) because anons get that too
458
				// and we shouldn't be putting timestamps in CDN-cached HTML
459
				if ( $group === 'user' ) {
460
					// Must setModules() before makeVersionQuery()
461
					$context->setVersion( $rl->makeVersionQuery( $context ) );
0 ignored issues
show
It seems like $rl->makeVersionQuery($context) targeting ResourceLoader::makeVersionQuery() can also be of type false; however, DerivativeResourceLoaderContext::setVersion() does only seem to accept string|null, did you maybe forget to handle an error condition?
Loading history...
462
				}
463
464
				$url = $rl->createLoaderURL( $source, $context, $extraQuery );
465
466
				// Decide whether to use 'style' or 'script' element
467
				if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
468
					$chunk = Html::linkedStyle( $url );
469
				} else {
470
					if ( $context->getRaw() || $isRaw ) {
471
						$chunk = Html::element( 'script', [
472
							// In SpecialJavaScriptTest, QUnit must load synchronous
473
							'async' => !isset( $extraQuery['sync'] ),
474
							'src' => $url
475
						] );
476
					} else {
477
						$chunk = ResourceLoader::makeInlineScript(
478
							Xml::encodeJsCall( 'mw.loader.load', [ $url ] )
0 ignored issues
show
It seems like \Xml::encodeJsCall('mw.loader.load', array($url)) targeting Xml::encodeJsCall() can also be of type false; however, ResourceLoader::makeInlineScript() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
479
						);
480
					}
481
				}
482
483
				if ( $group == 'noscript' ) {
484
					$chunks[] = Html::rawElement( 'noscript', [], $chunk );
0 ignored issues
show
It seems like $chunk defined by \ResourceLoader::makeInl...er.load', array($url))) on line 477 can also be of type object<WrappedString\WrappedString>; however, Html::rawElement() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
485
				} else {
486
					$chunks[] = $chunk;
487
				}
488
			}
489
		}
490
491
		return new WrappedStringList( "\n", $chunks );
492
	}
493
}
494