This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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
|
|||
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 /**
* @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. ![]() |
|||
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
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
|
|||
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
|
|||
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
|
|||
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
|
|||
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
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
|
|||
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
|
|||
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. ![]() |
|||
485 | } else { |
||
486 | $chunks[] = $chunk; |
||
487 | } |
||
488 | } |
||
489 | } |
||
490 | |||
491 | return new WrappedStringList( "\n", $chunks ); |
||
492 | } |
||
493 | } |
||
494 |
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 theid
property of an instance of theAccount
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.