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 | * @since 1.18 |
||
19 | * |
||
20 | * @author Alexandre Emsenhuber |
||
21 | * @author Daniel Friesen |
||
22 | * @file |
||
23 | */ |
||
24 | |||
25 | use Liuggio\StatsdClient\Factory\StatsdDataFactory; |
||
26 | use MediaWiki\Logger\LoggerFactory; |
||
27 | use MediaWiki\MediaWikiServices; |
||
28 | use Wikimedia\ScopedCallback; |
||
0 ignored issues
–
show
|
|||
29 | |||
30 | /** |
||
31 | * Group all the pieces relevant to the context of a request into one instance |
||
32 | */ |
||
33 | class RequestContext implements IContextSource, MutableContext { |
||
34 | /** |
||
35 | * @var WebRequest |
||
36 | */ |
||
37 | private $request; |
||
38 | |||
39 | /** |
||
40 | * @var Title |
||
41 | */ |
||
42 | private $title; |
||
43 | |||
44 | /** |
||
45 | * @var WikiPage |
||
46 | */ |
||
47 | private $wikipage; |
||
48 | |||
49 | /** |
||
50 | * @var OutputPage |
||
51 | */ |
||
52 | private $output; |
||
53 | |||
54 | /** |
||
55 | * @var User |
||
56 | */ |
||
57 | private $user; |
||
58 | |||
59 | /** |
||
60 | * @var Language |
||
61 | */ |
||
62 | private $lang; |
||
63 | |||
64 | /** |
||
65 | * @var Skin |
||
66 | */ |
||
67 | private $skin; |
||
68 | |||
69 | /** |
||
70 | * @var Timing |
||
71 | */ |
||
72 | private $timing; |
||
73 | |||
74 | /** |
||
75 | * @var Config |
||
76 | */ |
||
77 | private $config; |
||
78 | |||
79 | /** |
||
80 | * @var RequestContext |
||
81 | */ |
||
82 | private static $instance = null; |
||
83 | |||
84 | /** |
||
85 | * Set the Config object |
||
86 | * |
||
87 | * @param Config $c |
||
88 | */ |
||
89 | public function setConfig( Config $c ) { |
||
90 | $this->config = $c; |
||
91 | } |
||
92 | |||
93 | /** |
||
94 | * Get the Config object |
||
95 | * |
||
96 | * @return Config |
||
97 | */ |
||
98 | public function getConfig() { |
||
99 | if ( $this->config === null ) { |
||
100 | // @todo In the future, we could move this to WebStart.php so |
||
101 | // the Config object is ready for when initialization happens |
||
102 | $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); |
||
103 | } |
||
104 | |||
105 | return $this->config; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Set the WebRequest object |
||
110 | * |
||
111 | * @param WebRequest $r |
||
112 | */ |
||
113 | public function setRequest( WebRequest $r ) { |
||
114 | $this->request = $r; |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Get the WebRequest object |
||
119 | * |
||
120 | * @return WebRequest |
||
121 | */ |
||
122 | public function getRequest() { |
||
123 | if ( $this->request === null ) { |
||
124 | global $wgCommandLineMode; |
||
125 | // create the WebRequest object on the fly |
||
126 | if ( $wgCommandLineMode ) { |
||
127 | $this->request = new FauxRequest( [] ); |
||
128 | } else { |
||
129 | $this->request = new WebRequest(); |
||
130 | } |
||
131 | } |
||
132 | |||
133 | return $this->request; |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Get the Stats object |
||
138 | * |
||
139 | * @deprecated since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected) |
||
140 | * |
||
141 | * @return StatsdDataFactory |
||
142 | */ |
||
143 | public function getStats() { |
||
144 | return MediaWikiServices::getInstance()->getStatsdDataFactory(); |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Get the timing object |
||
149 | * |
||
150 | * @return Timing |
||
151 | */ |
||
152 | public function getTiming() { |
||
153 | if ( $this->timing === null ) { |
||
154 | $this->timing = new Timing( [ |
||
155 | 'logger' => LoggerFactory::getInstance( 'Timing' ) |
||
156 | ] ); |
||
157 | } |
||
158 | return $this->timing; |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * Set the Title object |
||
163 | * |
||
164 | * @param Title|null $title |
||
165 | */ |
||
166 | public function setTitle( Title $title = null ) { |
||
167 | $this->title = $title; |
||
168 | // Erase the WikiPage so a new one with the new title gets created. |
||
169 | $this->wikipage = null; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Get the Title object |
||
174 | * |
||
175 | * @return Title|null |
||
176 | */ |
||
177 | public function getTitle() { |
||
178 | if ( $this->title === null ) { |
||
179 | global $wgTitle; # fallback to $wg till we can improve this |
||
180 | $this->title = $wgTitle; |
||
181 | wfDebugLog( |
||
182 | 'GlobalTitleFail', |
||
183 | __METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.' |
||
184 | ); |
||
185 | } |
||
186 | |||
187 | return $this->title; |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * Check, if a Title object is set |
||
192 | * |
||
193 | * @since 1.25 |
||
194 | * @return bool |
||
195 | */ |
||
196 | public function hasTitle() { |
||
197 | return $this->title !== null; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Check whether a WikiPage object can be get with getWikiPage(). |
||
202 | * Callers should expect that an exception is thrown from getWikiPage() |
||
203 | * if this method returns false. |
||
204 | * |
||
205 | * @since 1.19 |
||
206 | * @return bool |
||
207 | */ |
||
208 | public function canUseWikiPage() { |
||
209 | if ( $this->wikipage ) { |
||
210 | // If there's a WikiPage object set, we can for sure get it |
||
211 | return true; |
||
212 | } |
||
213 | // Only pages with legitimate titles can have WikiPages. |
||
214 | // That usually means pages in non-virtual namespaces. |
||
215 | $title = $this->getTitle(); |
||
216 | return $title ? $title->canExist() : false; |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * Set the WikiPage object |
||
221 | * |
||
222 | * @since 1.19 |
||
223 | * @param WikiPage $p |
||
224 | */ |
||
225 | public function setWikiPage( WikiPage $p ) { |
||
226 | $pageTitle = $p->getTitle(); |
||
227 | if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) { |
||
228 | $this->setTitle( $pageTitle ); |
||
229 | } |
||
230 | // Defer this to the end since setTitle sets it to null. |
||
231 | $this->wikipage = $p; |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * Get the WikiPage object. |
||
236 | * May throw an exception if there's no Title object set or the Title object |
||
237 | * belongs to a special namespace that doesn't have WikiPage, so use first |
||
238 | * canUseWikiPage() to check whether this method can be called safely. |
||
239 | * |
||
240 | * @since 1.19 |
||
241 | * @throws MWException |
||
242 | * @return WikiPage |
||
243 | */ |
||
244 | public function getWikiPage() { |
||
245 | if ( $this->wikipage === null ) { |
||
246 | $title = $this->getTitle(); |
||
247 | if ( $title === null ) { |
||
248 | throw new MWException( __METHOD__ . ' called without Title object set' ); |
||
249 | } |
||
250 | $this->wikipage = WikiPage::factory( $title ); |
||
251 | } |
||
252 | |||
253 | return $this->wikipage; |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * @param OutputPage $o |
||
258 | */ |
||
259 | public function setOutput( OutputPage $o ) { |
||
260 | $this->output = $o; |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Get the OutputPage object |
||
265 | * |
||
266 | * @return OutputPage |
||
267 | */ |
||
268 | public function getOutput() { |
||
269 | if ( $this->output === null ) { |
||
270 | $this->output = new OutputPage( $this ); |
||
271 | } |
||
272 | |||
273 | return $this->output; |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Set the User object |
||
278 | * |
||
279 | * @param User $u |
||
280 | */ |
||
281 | public function setUser( User $u ) { |
||
282 | $this->user = $u; |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * Get the User object |
||
287 | * |
||
288 | * @return User |
||
289 | */ |
||
290 | public function getUser() { |
||
291 | if ( $this->user === null ) { |
||
292 | $this->user = User::newFromSession( $this->getRequest() ); |
||
293 | } |
||
294 | |||
295 | return $this->user; |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Accepts a language code and ensures it's sane. Outputs a cleaned up language |
||
300 | * code and replaces with $wgLanguageCode if not sane. |
||
301 | * @param string $code Language code |
||
302 | * @return string |
||
303 | */ |
||
304 | public static function sanitizeLangCode( $code ) { |
||
305 | global $wgLanguageCode; |
||
306 | |||
307 | // BCP 47 - letter case MUST NOT carry meaning |
||
308 | $code = strtolower( $code ); |
||
309 | |||
310 | # Validate $code |
||
311 | if ( !$code || !Language::isValidCode( $code ) || $code === 'qqq' ) { |
||
312 | wfDebug( "Invalid user language code\n" ); |
||
313 | $code = $wgLanguageCode; |
||
314 | } |
||
315 | |||
316 | return $code; |
||
317 | } |
||
318 | |||
319 | /** |
||
320 | * Set the Language object |
||
321 | * |
||
322 | * @param Language|string $l Language instance or language code |
||
323 | * @throws MWException |
||
324 | * @since 1.19 |
||
325 | */ |
||
326 | View Code Duplication | public function setLanguage( $l ) { |
|
327 | if ( $l instanceof Language ) { |
||
328 | $this->lang = $l; |
||
329 | } elseif ( is_string( $l ) ) { |
||
330 | $l = self::sanitizeLangCode( $l ); |
||
331 | $obj = Language::factory( $l ); |
||
332 | $this->lang = $obj; |
||
333 | } else { |
||
334 | throw new MWException( __METHOD__ . " was passed an invalid type of data." ); |
||
335 | } |
||
336 | } |
||
337 | |||
338 | /** |
||
339 | * Get the Language object. |
||
340 | * Initialization of user or request objects can depend on this. |
||
341 | * @return Language |
||
342 | * @throws Exception |
||
343 | * @since 1.19 |
||
344 | */ |
||
345 | public function getLanguage() { |
||
346 | if ( isset( $this->recursion ) ) { |
||
347 | trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING ); |
||
348 | $e = new Exception; |
||
349 | wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() ); |
||
350 | |||
351 | $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en'; |
||
352 | $this->lang = Language::factory( $code ); |
||
353 | } elseif ( $this->lang === null ) { |
||
354 | $this->recursion = true; |
||
355 | |||
356 | global $wgContLang; |
||
357 | |||
358 | try { |
||
359 | $request = $this->getRequest(); |
||
360 | $user = $this->getUser(); |
||
361 | |||
362 | $code = $request->getVal( 'uselang', 'user' ); |
||
363 | if ( $code === 'user' ) { |
||
364 | $code = $user->getOption( 'language' ); |
||
365 | } |
||
366 | $code = self::sanitizeLangCode( $code ); |
||
367 | |||
368 | Hooks::run( 'UserGetLanguageObject', [ $user, &$code, $this ] ); |
||
369 | |||
370 | if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) { |
||
371 | $this->lang = $wgContLang; |
||
372 | } else { |
||
373 | $obj = Language::factory( $code ); |
||
374 | $this->lang = $obj; |
||
375 | } |
||
376 | |||
377 | unset( $this->recursion ); |
||
378 | } |
||
379 | catch ( Exception $ex ) { |
||
380 | unset( $this->recursion ); |
||
381 | throw $ex; |
||
382 | } |
||
383 | } |
||
384 | |||
385 | return $this->lang; |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * Set the Skin object |
||
390 | * |
||
391 | * @param Skin $s |
||
392 | */ |
||
393 | public function setSkin( Skin $s ) { |
||
394 | $this->skin = clone $s; |
||
395 | $this->skin->setContext( $this ); |
||
396 | } |
||
397 | |||
398 | /** |
||
399 | * Get the Skin object |
||
400 | * |
||
401 | * @return Skin |
||
402 | */ |
||
403 | public function getSkin() { |
||
404 | if ( $this->skin === null ) { |
||
405 | $skin = null; |
||
406 | Hooks::run( 'RequestContextCreateSkin', [ $this, &$skin ] ); |
||
407 | $factory = SkinFactory::getDefaultInstance(); |
||
408 | |||
409 | // If the hook worked try to set a skin from it |
||
410 | if ( $skin instanceof Skin ) { |
||
411 | $this->skin = $skin; |
||
412 | } elseif ( is_string( $skin ) ) { |
||
413 | // Normalize the key, just in case the hook did something weird. |
||
414 | $normalized = Skin::normalizeKey( $skin ); |
||
415 | $this->skin = $factory->makeSkin( $normalized ); |
||
416 | } |
||
417 | |||
418 | // If this is still null (the hook didn't run or didn't work) |
||
419 | // then go through the normal processing to load a skin |
||
420 | if ( $this->skin === null ) { |
||
421 | if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) { |
||
422 | # get the user skin |
||
423 | $userSkin = $this->getUser()->getOption( 'skin' ); |
||
424 | $userSkin = $this->getRequest()->getVal( 'useskin', $userSkin ); |
||
425 | } else { |
||
426 | # if we're not allowing users to override, then use the default |
||
427 | $userSkin = $this->getConfig()->get( 'DefaultSkin' ); |
||
428 | } |
||
429 | |||
430 | // Normalize the key in case the user is passing gibberish |
||
431 | // or has old preferences (bug 69566). |
||
432 | $normalized = Skin::normalizeKey( $userSkin ); |
||
433 | |||
434 | // Skin::normalizeKey will also validate it, so |
||
435 | // this won't throw an exception |
||
436 | $this->skin = $factory->makeSkin( $normalized ); |
||
437 | } |
||
438 | |||
439 | // After all that set a context on whatever skin got created |
||
440 | $this->skin->setContext( $this ); |
||
441 | } |
||
442 | |||
443 | return $this->skin; |
||
444 | } |
||
445 | |||
446 | /** Helpful methods **/ |
||
447 | |||
448 | /** |
||
449 | * Get a Message object with context set |
||
450 | * Parameters are the same as wfMessage() |
||
451 | * |
||
452 | * @param mixed ... |
||
453 | * @return Message |
||
454 | */ |
||
455 | public function msg() { |
||
456 | $args = func_get_args(); |
||
457 | |||
458 | return call_user_func_array( 'wfMessage', $args )->setContext( $this ); |
||
459 | } |
||
460 | |||
461 | /** Static methods **/ |
||
462 | |||
463 | /** |
||
464 | * Get the RequestContext object associated with the main request |
||
465 | * |
||
466 | * @return RequestContext |
||
467 | */ |
||
468 | public static function getMain() { |
||
469 | if ( self::$instance === null ) { |
||
470 | self::$instance = new self; |
||
471 | } |
||
472 | |||
473 | return self::$instance; |
||
474 | } |
||
475 | |||
476 | /** |
||
477 | * Get the RequestContext object associated with the main request |
||
478 | * and gives a warning to the log, to find places, where a context maybe is missing. |
||
479 | * |
||
480 | * @param string $func |
||
481 | * @return RequestContext |
||
482 | * @since 1.24 |
||
483 | */ |
||
484 | public static function getMainAndWarn( $func = __METHOD__ ) { |
||
485 | wfDebug( $func . ' called without context. ' . |
||
486 | "Using RequestContext::getMain() for sanity\n" ); |
||
487 | |||
488 | return self::getMain(); |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * Resets singleton returned by getMain(). Should be called only from unit tests. |
||
493 | */ |
||
494 | public static function resetMain() { |
||
495 | if ( !( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ) ) ) { |
||
496 | throw new MWException( __METHOD__ . '() should be called only from unit tests!' ); |
||
497 | } |
||
498 | self::$instance = null; |
||
499 | } |
||
500 | |||
501 | /** |
||
502 | * Export the resolved user IP, HTTP headers, user ID, and session ID. |
||
503 | * The result will be reasonably sized to allow for serialization. |
||
504 | * |
||
505 | * @return array |
||
506 | * @since 1.21 |
||
507 | */ |
||
508 | public function exportSession() { |
||
509 | $session = MediaWiki\Session\SessionManager::getGlobalSession(); |
||
510 | return [ |
||
511 | 'ip' => $this->getRequest()->getIP(), |
||
512 | 'headers' => $this->getRequest()->getAllHeaders(), |
||
513 | 'sessionId' => $session->isPersistent() ? $session->getId() : '', |
||
514 | 'userId' => $this->getUser()->getId() |
||
515 | ]; |
||
516 | } |
||
517 | |||
518 | /** |
||
519 | * Import an client IP address, HTTP headers, user ID, and session ID |
||
520 | * |
||
521 | * This sets the current session, $wgUser, and $wgRequest from $params. |
||
522 | * Once the return value falls out of scope, the old context is restored. |
||
523 | * This method should only be called in contexts where there is no session |
||
524 | * ID or end user receiving the response (CLI or HTTP job runners). This |
||
525 | * is partly enforced, and is done so to avoid leaking cookies if certain |
||
526 | * error conditions arise. |
||
527 | * |
||
528 | * This is useful when background scripts inherit context when acting on |
||
529 | * behalf of a user. In general the 'sessionId' parameter should be set |
||
530 | * to an empty string unless session importing is *truly* needed. This |
||
531 | * feature is somewhat deprecated. |
||
532 | * |
||
533 | * @note suhosin.session.encrypt may interfere with this method. |
||
534 | * |
||
535 | * @param array $params Result of RequestContext::exportSession() |
||
536 | * @return ScopedCallback |
||
537 | * @throws MWException |
||
538 | * @since 1.21 |
||
539 | */ |
||
540 | public static function importScopedSession( array $params ) { |
||
541 | if ( strlen( $params['sessionId'] ) && |
||
542 | MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent() |
||
543 | ) { |
||
544 | // Sanity check to avoid sending random cookies for the wrong users. |
||
545 | // This method should only called by CLI scripts or by HTTP job runners. |
||
546 | throw new MWException( "Sessions can only be imported when none is active." ); |
||
547 | } elseif ( !IP::isValid( $params['ip'] ) ) { |
||
548 | throw new MWException( "Invalid client IP address '{$params['ip']}'." ); |
||
549 | } |
||
550 | |||
551 | if ( $params['userId'] ) { // logged-in user |
||
552 | $user = User::newFromId( $params['userId'] ); |
||
553 | $user->load(); |
||
554 | if ( !$user->getId() ) { |
||
555 | throw new MWException( "No user with ID '{$params['userId']}'." ); |
||
556 | } |
||
557 | } else { // anon user |
||
558 | $user = User::newFromName( $params['ip'], false ); |
||
559 | } |
||
560 | |||
561 | $importSessionFunc = function ( User $user, array $params ) { |
||
562 | global $wgRequest, $wgUser; |
||
563 | |||
564 | $context = RequestContext::getMain(); |
||
565 | |||
566 | // Commit and close any current session |
||
567 | if ( MediaWiki\Session\PHPSessionHandler::isEnabled() ) { |
||
568 | session_write_close(); // persist |
||
569 | session_id( '' ); // detach |
||
570 | $_SESSION = []; // clear in-memory array |
||
571 | } |
||
572 | |||
573 | // Get new session, if applicable |
||
574 | $session = null; |
||
575 | if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID |
||
576 | $manager = MediaWiki\Session\SessionManager::singleton(); |
||
577 | $session = $manager->getSessionById( $params['sessionId'], true ) |
||
578 | ?: $manager->getEmptySession(); |
||
579 | } |
||
580 | |||
581 | // Remove any user IP or agent information, and attach the request |
||
582 | // with the new session. |
||
583 | $context->setRequest( new FauxRequest( [], false, $session ) ); |
||
584 | $wgRequest = $context->getRequest(); // b/c |
||
585 | |||
586 | // Now that all private information is detached from the user, it should |
||
587 | // be safe to load the new user. If errors occur or an exception is thrown |
||
588 | // and caught (leaving the main context in a mixed state), there is no risk |
||
589 | // of the User object being attached to the wrong IP, headers, or session. |
||
590 | $context->setUser( $user ); |
||
591 | $wgUser = $context->getUser(); // b/c |
||
592 | if ( $session && MediaWiki\Session\PHPSessionHandler::isEnabled() ) { |
||
593 | session_id( $session->getId() ); |
||
594 | MediaWiki\quietCall( 'session_start' ); |
||
595 | } |
||
596 | $request = new FauxRequest( [], false, $session ); |
||
597 | $request->setIP( $params['ip'] ); |
||
598 | foreach ( $params['headers'] as $name => $value ) { |
||
599 | $request->setHeader( $name, $value ); |
||
600 | } |
||
601 | // Set the current context to use the new WebRequest |
||
602 | $context->setRequest( $request ); |
||
603 | $wgRequest = $context->getRequest(); // b/c |
||
604 | }; |
||
605 | |||
606 | // Stash the old session and load in the new one |
||
607 | $oUser = self::getMain()->getUser(); |
||
608 | $oParams = self::getMain()->exportSession(); |
||
609 | $oRequest = self::getMain()->getRequest(); |
||
610 | $importSessionFunc( $user, $params ); |
||
611 | |||
612 | // Set callback to save and close the new session and reload the old one |
||
613 | return new ScopedCallback( |
||
614 | function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) { |
||
615 | global $wgRequest; |
||
616 | $importSessionFunc( $oUser, $oParams ); |
||
617 | // Restore the exact previous Request object (instead of leaving FauxRequest) |
||
618 | RequestContext::getMain()->setRequest( $oRequest ); |
||
619 | $wgRequest = RequestContext::getMain()->getRequest(); // b/c |
||
620 | } |
||
621 | ); |
||
622 | } |
||
623 | |||
624 | /** |
||
625 | * Create a new extraneous context. The context is filled with information |
||
626 | * external to the current session. |
||
627 | * - Title is specified by argument |
||
628 | * - Request is a FauxRequest, or a FauxRequest can be specified by argument |
||
629 | * - User is an anonymous user, for separation IPv4 localhost is used |
||
630 | * - Language will be based on the anonymous user and request, may be content |
||
631 | * language or a uselang param in the fauxrequest data may change the lang |
||
632 | * - Skin will be based on the anonymous user, should be the wiki's default skin |
||
633 | * |
||
634 | * @param Title $title Title to use for the extraneous request |
||
635 | * @param WebRequest|array $request A WebRequest or data to use for a FauxRequest |
||
636 | * @return RequestContext |
||
637 | */ |
||
638 | public static function newExtraneousContext( Title $title, $request = [] ) { |
||
639 | $context = new self; |
||
640 | $context->setTitle( $title ); |
||
641 | if ( $request instanceof WebRequest ) { |
||
642 | $context->setRequest( $request ); |
||
643 | } else { |
||
644 | $context->setRequest( new FauxRequest( $request ) ); |
||
645 | } |
||
646 | $context->user = User::newFromName( '127.0.0.1', false ); |
||
647 | |||
648 | return $context; |
||
649 | } |
||
650 | } |
||
651 |
Let’s assume that you have a directory layout like this:
and let’s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: