MediaWiki REL1_34
RequestContext.php
Go to the documentation of this file.
1<?php
25use Wikimedia\AtEase\AtEase;
28use Wikimedia\ScopedCallback;
29
37 private $request;
38
42 private $title;
43
47 private $wikipage;
48
52 private $output;
53
57 private $user;
58
62 private $lang;
63
67 private $skin;
68
72 private $timing;
73
77 private $config;
78
82 private static $instance = null;
83
88 private $languageRecursion = false;
89
93 public function setConfig( Config $config ) {
94 $this->config = $config;
95 }
96
100 public function getConfig() {
101 if ( $this->config === null ) {
102 // @todo In the future, we could move this to WebStart.php so
103 // the Config object is ready for when initialization happens
104 $this->config = MediaWikiServices::getInstance()->getMainConfig();
105 }
106
107 return $this->config;
108 }
109
113 public function setRequest( WebRequest $request ) {
114 $this->request = $request;
115 }
116
120 public function getRequest() {
121 if ( $this->request === null ) {
122 global $wgCommandLineMode;
123 // create the WebRequest object on the fly
124 if ( $wgCommandLineMode ) {
125 $this->request = new FauxRequest( [] );
126 } else {
127 $this->request = new WebRequest();
128 }
129 }
130
131 return $this->request;
132 }
133
139 public function getStats() {
140 return MediaWikiServices::getInstance()->getStatsdDataFactory();
141 }
142
146 public function getTiming() {
147 if ( $this->timing === null ) {
148 $this->timing = new Timing( [
149 'logger' => LoggerFactory::getInstance( 'Timing' )
150 ] );
151 }
152 return $this->timing;
153 }
154
158 public function setTitle( Title $title = null ) {
159 $this->title = $title;
160 // Erase the WikiPage so a new one with the new title gets created.
161 $this->wikipage = null;
162 }
163
167 public function getTitle() {
168 if ( $this->title === null ) {
169 global $wgTitle; # fallback to $wg till we can improve this
170 $this->title = $wgTitle;
172 'GlobalTitleFail',
173 __METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.'
174 );
175 }
176
177 return $this->title;
178 }
179
186 public function hasTitle() {
187 return $this->title !== null;
188 }
189
198 public function canUseWikiPage() {
199 if ( $this->wikipage ) {
200 // If there's a WikiPage object set, we can for sure get it
201 return true;
202 }
203 // Only pages with legitimate titles can have WikiPages.
204 // That usually means pages in non-virtual namespaces.
205 $title = $this->getTitle();
206 return $title ? $title->canExist() : false;
207 }
208
213 public function setWikiPage( WikiPage $wikiPage ) {
214 $pageTitle = $wikiPage->getTitle();
215 if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) {
216 $this->setTitle( $pageTitle );
217 }
218 // Defer this to the end since setTitle sets it to null.
219 $this->wikipage = $wikiPage;
220 }
221
232 public function getWikiPage() {
233 if ( $this->wikipage === null ) {
234 $title = $this->getTitle();
235 if ( $title === null ) {
236 throw new MWException( __METHOD__ . ' called without Title object set' );
237 }
238 $this->wikipage = WikiPage::factory( $title );
239 }
240
241 return $this->wikipage;
242 }
243
247 public function setOutput( OutputPage $output ) {
248 $this->output = $output;
249 }
250
254 public function getOutput() {
255 if ( $this->output === null ) {
256 $this->output = new OutputPage( $this );
257 }
258
259 return $this->output;
260 }
261
265 public function setUser( User $user ) {
266 $this->user = $user;
267 // Invalidate cached user interface language
268 $this->lang = null;
269 }
270
274 public function getUser() {
275 if ( $this->user === null ) {
276 $this->user = User::newFromSession( $this->getRequest() );
277 }
278
279 return $this->user;
280 }
281
288 public static function sanitizeLangCode( $code ) {
289 global $wgLanguageCode;
290
291 // BCP 47 - letter case MUST NOT carry meaning
292 $code = strtolower( $code );
293
294 # Validate $code
295 if ( !$code || !Language::isValidCode( $code ) || $code === 'qqq' ) {
296 $code = $wgLanguageCode;
297 }
298
299 return $code;
300 }
301
307 public function setLanguage( $language ) {
308 if ( $language instanceof Language ) {
309 $this->lang = $language;
310 } elseif ( is_string( $language ) ) {
311 $language = self::sanitizeLangCode( $language );
312 $obj = Language::factory( $language );
313 $this->lang = $obj;
314 } else {
315 throw new MWException( __METHOD__ . " was passed an invalid type of data." );
316 }
317 }
318
326 public function getLanguage() {
327 if ( $this->languageRecursion === true ) {
328 trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING );
329 $e = new Exception;
330 wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() );
331
332 $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en';
333 $this->lang = Language::factory( $code );
334 } elseif ( $this->lang === null ) {
335 $this->languageRecursion = true;
336
337 try {
338 $request = $this->getRequest();
339 $user = $this->getUser();
340
341 // Optimisation: Avoid slow getVal(), this isn't user-generated content.
342 $code = $request->getRawVal( 'uselang', 'user' );
343 if ( $code === 'user' ) {
344 $code = $user->getOption( 'language' );
345 }
346 $code = self::sanitizeLangCode( $code );
347
348 Hooks::run( 'UserGetLanguageObject', [ $user, &$code, $this ] );
349
350 if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) {
351 $this->lang = MediaWikiServices::getInstance()->getContentLanguage();
352 } else {
353 $obj = Language::factory( $code );
354 $this->lang = $obj;
355 }
356 } finally {
357 $this->languageRecursion = false;
358 }
359 }
360
361 return $this->lang;
362 }
363
367 public function setSkin( Skin $skin ) {
368 $this->skin = clone $skin;
369 $this->skin->setContext( $this );
370 }
371
375 public function getSkin() {
376 if ( $this->skin === null ) {
377 $skin = null;
378 Hooks::run( 'RequestContextCreateSkin', [ $this, &$skin ] );
379 $factory = MediaWikiServices::getInstance()->getSkinFactory();
380
381 if ( $skin instanceof Skin ) {
382 // The hook provided a skin object
383 $this->skin = $skin;
384 } elseif ( is_string( $skin ) ) {
385 // The hook provided a skin name
386 // Normalize the key, just in case the hook did something weird.
387 $normalized = Skin::normalizeKey( $skin );
388 $this->skin = $factory->makeSkin( $normalized );
389 } else {
390 // No hook override, go through normal processing
391 if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
392 $userSkin = $this->getUser()->getOption( 'skin' );
393 // Optimisation: Avoid slow getVal(), this isn't user-generated content.
394 $userSkin = $this->getRequest()->getRawVal( 'useskin', $userSkin );
395 } else {
396 $userSkin = $this->getConfig()->get( 'DefaultSkin' );
397 }
398
399 // Normalize the key in case the user is passing gibberish query params
400 // or has old user preferences (T71566).
401 // Skin::normalizeKey will also validate it, so makeSkin() won't throw.
402 $normalized = Skin::normalizeKey( $userSkin );
403 $this->skin = $factory->makeSkin( $normalized );
404 }
405
406 // After all that set a context on whatever skin got created
407 $this->skin->setContext( $this );
408 }
409
410 return $this->skin;
411 }
412
422 public function msg( $key, ...$params ) {
423 return wfMessage( $key, ...$params )->setContext( $this );
424 }
425
431 public static function getMain() {
432 if ( self::$instance === null ) {
433 self::$instance = new self;
434 }
435
436 return self::$instance;
437 }
438
447 public static function getMainAndWarn( $func = __METHOD__ ) {
448 wfDebug( $func . ' called without context. ' .
449 "Using RequestContext::getMain() for sanity\n" );
450
451 return self::getMain();
452 }
453
457 public static function resetMain() {
458 if ( !( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ) ) ) {
459 throw new MWException( __METHOD__ . '() should be called only from unit tests!' );
460 }
461 self::$instance = null;
462 }
463
471 public function exportSession() {
472 $session = MediaWiki\Session\SessionManager::getGlobalSession();
473 return [
474 'ip' => $this->getRequest()->getIP(),
475 'headers' => $this->getRequest()->getAllHeaders(),
476 'sessionId' => $session->isPersistent() ? $session->getId() : '',
477 'userId' => $this->getUser()->getId()
478 ];
479 }
480
503 public static function importScopedSession( array $params ) {
504 if ( strlen( $params['sessionId'] ) &&
505 MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent()
506 ) {
507 // Sanity check to avoid sending random cookies for the wrong users.
508 // This method should only called by CLI scripts or by HTTP job runners.
509 throw new MWException( "Sessions can only be imported when none is active." );
510 } elseif ( !IP::isValid( $params['ip'] ) ) {
511 throw new MWException( "Invalid client IP address '{$params['ip']}'." );
512 }
513
514 if ( $params['userId'] ) { // logged-in user
515 $user = User::newFromId( $params['userId'] );
516 $user->load();
517 if ( !$user->getId() ) {
518 throw new MWException( "No user with ID '{$params['userId']}'." );
519 }
520 } else { // anon user
521 $user = User::newFromName( $params['ip'], false );
522 }
523
524 $importSessionFunc = function ( User $user, array $params ) {
525 global $wgRequest, $wgUser;
526
528
529 // Commit and close any current session
530 if ( MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
531 session_write_close(); // persist
532 session_id( '' ); // detach
533 $_SESSION = []; // clear in-memory array
534 }
535
536 // Get new session, if applicable
537 $session = null;
538 if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID
539 $manager = MediaWiki\Session\SessionManager::singleton();
540 $session = $manager->getSessionById( $params['sessionId'], true )
541 ?: $manager->getEmptySession();
542 }
543
544 // Remove any user IP or agent information, and attach the request
545 // with the new session.
546 $context->setRequest( new FauxRequest( [], false, $session ) );
547 $wgRequest = $context->getRequest(); // b/c
548
549 // Now that all private information is detached from the user, it should
550 // be safe to load the new user. If errors occur or an exception is thrown
551 // and caught (leaving the main context in a mixed state), there is no risk
552 // of the User object being attached to the wrong IP, headers, or session.
553 $context->setUser( $user );
554 $wgUser = $context->getUser(); // b/c
555 if ( $session && MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
556 session_id( $session->getId() );
557 AtEase::quietCall( 'session_start' );
558 }
559 $request = new FauxRequest( [], false, $session );
560 $request->setIP( $params['ip'] );
561 foreach ( $params['headers'] as $name => $value ) {
562 $request->setHeader( $name, $value );
563 }
564 // Set the current context to use the new WebRequest
565 $context->setRequest( $request );
566 $wgRequest = $context->getRequest(); // b/c
567 };
568
569 // Stash the old session and load in the new one
570 $oUser = self::getMain()->getUser();
571 $oParams = self::getMain()->exportSession();
572 $oRequest = self::getMain()->getRequest();
573 $importSessionFunc( $user, $params );
574
575 // Set callback to save and close the new session and reload the old one
576 return new ScopedCallback(
577 function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) {
578 global $wgRequest;
579 $importSessionFunc( $oUser, $oParams );
580 // Restore the exact previous Request object (instead of leaving FauxRequest)
581 RequestContext::getMain()->setRequest( $oRequest );
582 $wgRequest = RequestContext::getMain()->getRequest(); // b/c
583 }
584 );
585 }
586
601 public static function newExtraneousContext( Title $title, $request = [] ) {
602 $context = new self;
603 $context->setTitle( $title );
604 if ( $request instanceof WebRequest ) {
605 $context->setRequest( $request );
606 } else {
607 $context->setRequest( new FauxRequest( $request ) );
608 }
609 $context->user = User::newFromName( '127.0.0.1', false );
610
611 return $context;
612 }
613}
getUser()
$wgLanguageCode
Site language code.
global $wgCommandLineMode
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:751
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Definition api.php:58
setContext(IContextSource $context)
WebRequest clone which takes values from a provided array.
Internationalisation code.
Definition Language.php:37
MediaWiki exception.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
This is one of the Core classes and should be read at least once by any new developers.
Group all the pieces relevant to the context of a request into one instance.
static getMainAndWarn( $func=__METHOD__)
Get the RequestContext object associated with the main request and gives a warning to the log,...
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
OutputPage $output
static importScopedSession(array $params)
Import an client IP address, HTTP headers, user ID, and session ID.
WebRequest $request
setUser(User $user)
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
static RequestContext $instance
static sanitizeLangCode( $code)
Accepts a language code and ensures it's sane.
setConfig(Config $config)
hasTitle()
Check, if a Title object is set.
bool $languageRecursion
Boolean flag to guard against recursion in getLanguage.
setTitle(Title $title=null)
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
exportSession()
Export the resolved user IP, HTTP headers, user ID, and session ID.
setOutput(OutputPage $output)
setWikiPage(WikiPage $wikiPage)
static resetMain()
Resets singleton returned by getMain().
static getMain()
Get the RequestContext object associated with the main request.
getLanguage()
Get the Language object.
setRequest(WebRequest $request)
getWikiPage()
Get the WikiPage object.
setLanguage( $language)
setSkin(Skin $skin)
The main skin class which provides methods and properties for all other skins.
Definition Skin.php:38
An interface to help developers measure the performance of their applications.
Definition Timing.php:45
Represents a title within MediaWiki.
Definition Title.php:42
canExist()
Is this in a namespace that allows actual pages?
Definition Title.php:1186
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
getId()
Get the user's ID.
Definition User.php:2335
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition User.php:3022
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition User.php:306
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
getRawVal( $name, $default=null)
Fetch a scalar from the input without normalization, or return $default if it's not set.
Class representing a MediaWiki article and history.
Definition WikiPage.php:47
getTitle()
Get the title object of the article.
Definition WikiPage.php:298
Interface for configuration instances.
Definition Config.php:28
Interface for objects which can provide a MediaWiki context on request.
$context
Definition load.php:45
This class serves as a utility class for this extension.
if(!isset( $args[0])) $lang