MediaWiki REL1_35
RequestContext.php
Go to the documentation of this file.
1<?php
27use Wikimedia\AtEase\AtEase;
28use Wikimedia\IPUtils;
29use Wikimedia\ScopedCallback;
30
42 private $request;
43
47 private $title;
48
52 private $wikipage;
53
57 private $output;
58
62 private $user;
63
67 private $lang;
68
72 private $skin;
73
77 private $timing;
78
82 private $config;
83
87 private static $instance = null;
88
93 private $languageRecursion = false;
94
98 public function setConfig( Config $config ) {
99 $this->config = $config;
100 }
101
105 public function getConfig() {
106 if ( $this->config === null ) {
107 // @todo In the future, we could move this to WebStart.php so
108 // the Config object is ready for when initialization happens
109 $this->config = MediaWikiServices::getInstance()->getMainConfig();
110 }
111
112 return $this->config;
113 }
114
118 public function setRequest( WebRequest $request ) {
119 $this->request = $request;
120 }
121
125 public function getRequest() {
126 if ( $this->request === null ) {
127 global $wgCommandLineMode;
128 // create the WebRequest object on the fly
129 if ( $wgCommandLineMode ) {
130 $this->request = new FauxRequest( [] );
131 } else {
132 $this->request = new WebRequest();
133 }
134 }
135
136 return $this->request;
137 }
138
144 public function getStats() {
145 return MediaWikiServices::getInstance()->getStatsdDataFactory();
146 }
147
151 public function getTiming() {
152 if ( $this->timing === null ) {
153 $this->timing = new Timing( [
154 'logger' => LoggerFactory::getInstance( 'Timing' )
155 ] );
156 }
157 return $this->timing;
158 }
159
163 public function setTitle( Title $title = null ) {
164 $this->title = $title;
165 // Erase the WikiPage so a new one with the new title gets created.
166 $this->wikipage = null;
167 }
168
172 public function getTitle() {
173 if ( $this->title === null ) {
174 global $wgTitle; # fallback to $wg till we can improve this
175 $this->title = $wgTitle;
176 $logger = LoggerFactory::getInstance( 'GlobalTitleFail' );
177 $logger->info(
178 __METHOD__ . ' called with no title set.',
179 [ 'exception' => new Exception ]
180 );
181 }
182
183 return $this->title;
184 }
185
192 public function hasTitle() {
193 return $this->title !== null;
194 }
195
204 public function canUseWikiPage() {
205 if ( $this->wikipage ) {
206 // If there's a WikiPage object set, we can for sure get it
207 return true;
208 }
209 // Only pages with legitimate titles can have WikiPages.
210 // That usually means pages in non-virtual namespaces.
211 $title = $this->getTitle();
212 return $title ? $title->canExist() : false;
213 }
214
219 public function setWikiPage( WikiPage $wikiPage ) {
220 $pageTitle = $wikiPage->getTitle();
221 if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) {
222 $this->setTitle( $pageTitle );
223 }
224 // Defer this to the end since setTitle sets it to null.
225 $this->wikipage = $wikiPage;
226 }
227
238 public function getWikiPage() {
239 if ( $this->wikipage === null ) {
240 $title = $this->getTitle();
241 if ( $title === null ) {
242 throw new MWException( __METHOD__ . ' called without Title object set' );
243 }
244 $this->wikipage = WikiPage::factory( $title );
245 }
246
247 return $this->wikipage;
248 }
249
253 public function setOutput( OutputPage $output ) {
254 $this->output = $output;
255 }
256
260 public function getOutput() {
261 if ( $this->output === null ) {
262 $this->output = new OutputPage( $this );
263 }
264
265 return $this->output;
266 }
267
271 public function setUser( User $user ) {
272 $this->user = $user;
273 // Invalidate cached user interface language
274 $this->lang = null;
275 }
276
280 public function getUser() {
281 if ( $this->user === null ) {
282 $this->user = User::newFromSession( $this->getRequest() );
283 }
284
285 return $this->user;
286 }
287
294 public static function sanitizeLangCode( $code ) {
295 global $wgLanguageCode;
296
297 // BCP 47 - letter case MUST NOT carry meaning
298 $code = strtolower( $code );
299
300 # Validate $code
301 if ( !$code
302 || !MediaWikiServices::getInstance()->getLanguageNameUtils()
303 ->isValidCode( $code )
304 || $code === 'qqq'
305 ) {
306 $code = $wgLanguageCode;
307 }
308
309 return $code;
310 }
311
317 public function setLanguage( $language ) {
318 if ( $language instanceof Language ) {
319 $this->lang = $language;
320 } elseif ( is_string( $language ) ) {
321 $language = self::sanitizeLangCode( $language );
322 $obj = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $language );
323 $this->lang = $obj;
324 } else {
325 throw new MWException( __METHOD__ . " was passed an invalid type of data." );
326 }
327 }
328
336 public function getLanguage() {
337 if ( $this->languageRecursion === true ) {
338 trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING );
339 $e = new Exception;
340 wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() );
341
342 $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en';
343 $this->lang = MediaWikiServices::getInstance()->getLanguageFactory()
344 ->getLanguage( $code );
345 } elseif ( $this->lang === null ) {
346 $this->languageRecursion = true;
347
348 try {
349 $request = $this->getRequest();
350 $user = $this->getUser();
351
352 // Optimisation: Avoid slow getVal(), this isn't user-generated content.
353 $code = $request->getRawVal( 'uselang', 'user' );
354 if ( $code === 'user' ) {
355 $code = $user->getOption( 'language' );
356 }
357
358 // There are certain characters we don't allow in language code strings,
359 // but by and large almost any valid UTF-8 string will makes it past
360 // this check and the LanguageNameUtils::isValidCode method it uses.
361 // This is to support on-wiki interface message overrides for
362 // non-existent language codes. Also known as "Uselang hacks".
363 // See <https://www.mediawiki.org/wiki/Manual:Uselang_hack>
364 // For something like "en-whatever" or "de-whatever" it will end up
365 // with a mostly "en" or "de" interface, but with an extra layer of
366 // possible MessageCache overrides from `MediaWiki:*/<code>` titles.
367 // While non-ASCII works here, it is required that they are in
368 // NFC form given this will not convert to normalised form.
369 $code = self::sanitizeLangCode( $code );
370
371 Hooks::runner()->onUserGetLanguageObject( $user, $code, $this );
372
373 if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) {
374 $this->lang = MediaWikiServices::getInstance()->getContentLanguage();
375 } else {
376 $obj = MediaWikiServices::getInstance()->getLanguageFactory()
377 ->getLanguage( $code );
378 $this->lang = $obj;
379 }
380 } finally {
381 $this->languageRecursion = false;
382 }
383 }
384
385 return $this->lang;
386 }
387
391 public function setSkin( Skin $skin ) {
392 $this->skin = clone $skin;
393 $this->skin->setContext( $this );
394 }
395
399 public function getSkin() {
400 if ( $this->skin === null ) {
401 $skin = null;
402 Hooks::runner()->onRequestContextCreateSkin( $this, $skin );
403 $factory = MediaWikiServices::getInstance()->getSkinFactory();
404
405 if ( $skin instanceof Skin ) {
406 // The hook provided a skin object
407 $this->skin = $skin;
408 } elseif ( is_string( $skin ) ) {
409 // The hook provided a skin name
410 // Normalize the key, just in case the hook did something weird.
411 $normalized = Skin::normalizeKey( $skin );
412 $this->skin = $factory->makeSkin( $normalized );
413 } else {
414 // No hook override, go through normal processing
415 if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
416 $userSkin = $this->getUser()->getOption( 'skin' );
417 // Optimisation: Avoid slow getVal(), this isn't user-generated content.
418 $userSkin = $this->getRequest()->getRawVal( 'useskin', $userSkin );
419 } else {
420 $userSkin = $this->getConfig()->get( 'DefaultSkin' );
421 }
422
423 // Normalize the key in case the user is passing gibberish query params
424 // or has old user preferences (T71566).
425 // Skin::normalizeKey will also validate it, so makeSkin() won't throw.
426 $normalized = Skin::normalizeKey( $userSkin );
427 $this->skin = $factory->makeSkin( $normalized );
428 }
429
430 // After all that set a context on whatever skin got created
431 $this->skin->setContext( $this );
432 }
433
434 return $this->skin;
435 }
436
446 public function msg( $key, ...$params ) {
447 return wfMessage( $key, ...$params )->setContext( $this );
448 }
449
455 public static function getMain() {
456 if ( self::$instance === null ) {
457 self::$instance = new self;
458 }
459
460 return self::$instance;
461 }
462
471 public static function getMainAndWarn( $func = __METHOD__ ) {
472 wfDebug( $func . ' called without context. ' .
473 "Using RequestContext::getMain() for sanity" );
474
475 return self::getMain();
476 }
477
481 public static function resetMain() {
482 if ( !( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ) ) ) {
483 throw new MWException( __METHOD__ . '() should be called only from unit tests!' );
484 }
485 self::$instance = null;
486 }
487
495 public function exportSession() {
496 $session = MediaWiki\Session\SessionManager::getGlobalSession();
497 return [
498 'ip' => $this->getRequest()->getIP(),
499 'headers' => $this->getRequest()->getAllHeaders(),
500 'sessionId' => $session->isPersistent() ? $session->getId() : '',
501 'userId' => $this->getUser()->getId()
502 ];
503 }
504
527 public static function importScopedSession( array $params ) {
528 if ( strlen( $params['sessionId'] ) &&
529 MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent()
530 ) {
531 // Sanity check to avoid sending random cookies for the wrong users.
532 // This method should only called by CLI scripts or by HTTP job runners.
533 throw new MWException( "Sessions can only be imported when none is active." );
534 } elseif ( !IPUtils::isValid( $params['ip'] ) ) {
535 throw new MWException( "Invalid client IP address '{$params['ip']}'." );
536 }
537
538 if ( $params['userId'] ) { // logged-in user
539 $user = User::newFromId( $params['userId'] );
540 $user->load();
541 if ( !$user->getId() ) {
542 throw new MWException( "No user with ID '{$params['userId']}'." );
543 }
544 } else { // anon user
545 $user = User::newFromName( $params['ip'], false );
546 }
547
548 $importSessionFunc = function ( User $user, array $params ) {
549 global $wgRequest, $wgUser;
550
551 $context = RequestContext::getMain();
552
553 // Commit and close any current session
554 if ( MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
555 session_write_close(); // persist
556 session_id( '' ); // detach
557 $_SESSION = []; // clear in-memory array
558 }
559
560 // Get new session, if applicable
561 $session = null;
562 if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID
563 $manager = MediaWiki\Session\SessionManager::singleton();
564 $session = $manager->getSessionById( $params['sessionId'], true )
565 ?: $manager->getEmptySession();
566 }
567
568 // Remove any user IP or agent information, and attach the request
569 // with the new session.
570 $context->setRequest( new FauxRequest( [], false, $session ) );
571 $wgRequest = $context->getRequest(); // b/c
572
573 // Now that all private information is detached from the user, it should
574 // be safe to load the new user. If errors occur or an exception is thrown
575 // and caught (leaving the main context in a mixed state), there is no risk
576 // of the User object being attached to the wrong IP, headers, or session.
577 $context->setUser( $user );
578 $wgUser = $context->getUser(); // b/c
579 if ( $session && MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
580 session_id( $session->getId() );
581 AtEase::quietCall( 'session_start' );
582 }
583 $request = new FauxRequest( [], false, $session );
584 $request->setIP( $params['ip'] );
585 foreach ( $params['headers'] as $name => $value ) {
586 $request->setHeader( $name, $value );
587 }
588 // Set the current context to use the new WebRequest
589 $context->setRequest( $request );
590 $wgRequest = $context->getRequest(); // b/c
591 };
592
593 // Stash the old session and load in the new one
594 $oUser = self::getMain()->getUser();
595 $oParams = self::getMain()->exportSession();
596 $oRequest = self::getMain()->getRequest();
597 $importSessionFunc( $user, $params );
598
599 // Set callback to save and close the new session and reload the old one
600 return new ScopedCallback(
601 function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) {
602 global $wgRequest;
603 $importSessionFunc( $oUser, $oParams );
604 // Restore the exact previous Request object (instead of leaving FauxRequest)
605 RequestContext::getMain()->setRequest( $oRequest );
606 $wgRequest = RequestContext::getMain()->getRequest(); // b/c
607 }
608 );
609 }
610
625 public static function newExtraneousContext( Title $title, $request = [] ) {
626 $context = new self;
627 $context->setTitle( $title );
628 if ( $request instanceof WebRequest ) {
629 $context->setRequest( $request );
630 } else {
631 $context->setRequest( new FauxRequest( $request ) );
632 }
633 $context->user = User::newFromName( '127.0.0.1', false );
634
635 return $context;
636 }
637}
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.
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.
$wgTitle
Definition Setup.php:799
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:643
setContext(IContextSource $context)
WebRequest clone which takes values from a provided array.
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:41
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 @newable.
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:41
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()
Can this title represent a page in the wiki's database?
Definition Title.php:1195
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
getId()
Get the user's ID.
Definition User.php:2121
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition User.php:2665
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:565
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition User.php:695
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition User.php:321
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:51
getTitle()
Get the title object of the article.
Definition WikiPage.php:318
Interface for configuration instances.
Definition Config.php:30
Interface for objects which can provide a MediaWiki context on request.
A helper class for throttling authentication attempts.
if(!isset( $args[0])) $lang