MediaWiki  master
RequestContext.php
Go to the documentation of this file.
1 <?php
27 use Wikimedia\AtEase\AtEase;
28 use Wikimedia\IPUtils;
29 use Wikimedia\ScopedCallback;
30 
38  private $request;
39 
43  private $title;
44 
48  private $wikipage;
49 
53  private $output;
54 
58  private $user;
59 
63  private $lang;
64 
68  private $skin;
69 
73  private $timing;
74 
78  private $config;
79 
83  private static $instance = null;
84 
89  private $languageRecursion = false;
90 
94  public function setConfig( Config $config ) {
95  $this->config = $config;
96  }
97 
101  public function getConfig() {
102  if ( $this->config === null ) {
103  // @todo In the future, we could move this to WebStart.php so
104  // the Config object is ready for when initialization happens
105  $this->config = MediaWikiServices::getInstance()->getMainConfig();
106  }
107 
108  return $this->config;
109  }
110 
114  public function setRequest( WebRequest $request ) {
115  $this->request = $request;
116  }
117 
121  public function getRequest() {
122  if ( $this->request === null ) {
123  global $wgCommandLineMode;
124  // create the WebRequest object on the fly
125  if ( $wgCommandLineMode ) {
126  $this->request = new FauxRequest( [] );
127  } else {
128  $this->request = new WebRequest();
129  }
130  }
131 
132  return $this->request;
133  }
134 
140  public function getStats() {
141  return MediaWikiServices::getInstance()->getStatsdDataFactory();
142  }
143 
147  public function getTiming() {
148  if ( $this->timing === null ) {
149  $this->timing = new Timing( [
150  'logger' => LoggerFactory::getInstance( 'Timing' )
151  ] );
152  }
153  return $this->timing;
154  }
155 
159  public function setTitle( Title $title = null ) {
160  $this->title = $title;
161  // Erase the WikiPage so a new one with the new title gets created.
162  $this->wikipage = null;
163  }
164 
168  public function getTitle() {
169  if ( $this->title === null ) {
170  global $wgTitle; # fallback to $wg till we can improve this
171  $this->title = $wgTitle;
172  $logger = LoggerFactory::getInstance( 'GlobalTitleFail' );
173  $logger->info(
174  __METHOD__ . ' called with no title set.',
175  [ 'exception' => new Exception ]
176  );
177  }
178 
179  return $this->title;
180  }
181 
188  public function hasTitle() {
189  return $this->title !== null;
190  }
191 
200  public function canUseWikiPage() {
201  if ( $this->wikipage ) {
202  // If there's a WikiPage object set, we can for sure get it
203  return true;
204  }
205  // Only pages with legitimate titles can have WikiPages.
206  // That usually means pages in non-virtual namespaces.
207  $title = $this->getTitle();
208  return $title ? $title->canExist() : false;
209  }
210 
215  public function setWikiPage( WikiPage $wikiPage ) {
216  $pageTitle = $wikiPage->getTitle();
217  if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) {
218  $this->setTitle( $pageTitle );
219  }
220  // Defer this to the end since setTitle sets it to null.
221  $this->wikipage = $wikiPage;
222  }
223 
234  public function getWikiPage() {
235  if ( $this->wikipage === null ) {
236  $title = $this->getTitle();
237  if ( $title === null ) {
238  throw new MWException( __METHOD__ . ' called without Title object set' );
239  }
240  $this->wikipage = WikiPage::factory( $title );
241  }
242 
243  return $this->wikipage;
244  }
245 
249  public function setOutput( OutputPage $output ) {
250  $this->output = $output;
251  }
252 
256  public function getOutput() {
257  if ( $this->output === null ) {
258  $this->output = new OutputPage( $this );
259  }
260 
261  return $this->output;
262  }
263 
267  public function setUser( User $user ) {
268  $this->user = $user;
269  // Invalidate cached user interface language
270  $this->lang = null;
271  }
272 
276  public function getUser() {
277  if ( $this->user === null ) {
278  $this->user = User::newFromSession( $this->getRequest() );
279  }
280 
281  return $this->user;
282  }
283 
290  public static function sanitizeLangCode( $code ) {
291  global $wgLanguageCode;
292 
293  // BCP 47 - letter case MUST NOT carry meaning
294  $code = strtolower( $code );
295 
296  # Validate $code
297  if ( !$code
298  || !MediaWikiServices::getInstance()->getLanguageNameUtils()
299  ->isValidCode( $code )
300  || $code === 'qqq'
301  ) {
302  $code = $wgLanguageCode;
303  }
304 
305  return $code;
306  }
307 
313  public function setLanguage( $language ) {
314  if ( $language instanceof Language ) {
315  $this->lang = $language;
316  } elseif ( is_string( $language ) ) {
317  $language = self::sanitizeLangCode( $language );
318  $obj = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $language );
319  $this->lang = $obj;
320  } else {
321  throw new MWException( __METHOD__ . " was passed an invalid type of data." );
322  }
323  }
324 
332  public function getLanguage() {
333  if ( $this->languageRecursion === true ) {
334  trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING );
335  $e = new Exception;
336  wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() );
337 
338  $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en';
339  $this->lang = MediaWikiServices::getInstance()->getLanguageFactory()
340  ->getLanguage( $code );
341  } elseif ( $this->lang === null ) {
342  $this->languageRecursion = true;
343 
344  try {
345  $request = $this->getRequest();
346  $user = $this->getUser();
347 
348  // Optimisation: Avoid slow getVal(), this isn't user-generated content.
349  $code = $request->getRawVal( 'uselang', 'user' );
350  if ( $code === 'user' ) {
351  $code = $user->getOption( 'language' );
352  }
353 
354  // There are certain characters we don't allow in language code strings,
355  // but by and large almost any valid UTF-8 string will makes it past
356  // this check and the LanguageNameUtils::isValidCode method it uses.
357  // This is to support on-wiki interface message overrides for
358  // non-existent language codes. Also known as "Uselang hacks".
359  // See <https://www.mediawiki.org/wiki/Manual:Uselang_hack>
360  // For something like "en-whatever" or "de-whatever" it will end up
361  // with a mostly "en" or "de" interface, but with an extra layer of
362  // possible MessageCache overrides from `MediaWiki:*/<code>` titles.
363  // While non-ASCII works here, it is required that they are in
364  // NFC form given this will not convert to normalised form.
365  $code = self::sanitizeLangCode( $code );
366 
367  Hooks::run( 'UserGetLanguageObject', [ $user, &$code, $this ] );
368 
369  if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) {
370  $this->lang = MediaWikiServices::getInstance()->getContentLanguage();
371  } else {
372  $obj = MediaWikiServices::getInstance()->getLanguageFactory()
373  ->getLanguage( $code );
374  $this->lang = $obj;
375  }
376  } finally {
377  $this->languageRecursion = false;
378  }
379  }
380 
381  return $this->lang;
382  }
383 
387  public function setSkin( Skin $skin ) {
388  $this->skin = clone $skin;
389  $this->skin->setContext( $this );
390  }
391 
395  public function getSkin() {
396  if ( $this->skin === null ) {
397  $skin = null;
398  Hooks::run( 'RequestContextCreateSkin', [ $this, &$skin ] );
399  $factory = MediaWikiServices::getInstance()->getSkinFactory();
400 
401  if ( $skin instanceof Skin ) {
402  // The hook provided a skin object
403  $this->skin = $skin;
404  } elseif ( is_string( $skin ) ) {
405  // The hook provided a skin name
406  // Normalize the key, just in case the hook did something weird.
407  $normalized = Skin::normalizeKey( $skin );
408  $this->skin = $factory->makeSkin( $normalized );
409  } else {
410  // No hook override, go through normal processing
411  if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
412  $userSkin = $this->getUser()->getOption( 'skin' );
413  // Optimisation: Avoid slow getVal(), this isn't user-generated content.
414  $userSkin = $this->getRequest()->getRawVal( 'useskin', $userSkin );
415  } else {
416  $userSkin = $this->getConfig()->get( 'DefaultSkin' );
417  }
418 
419  // Normalize the key in case the user is passing gibberish query params
420  // or has old user preferences (T71566).
421  // Skin::normalizeKey will also validate it, so makeSkin() won't throw.
422  $normalized = Skin::normalizeKey( $userSkin );
423  $this->skin = $factory->makeSkin( $normalized );
424  }
425 
426  // After all that set a context on whatever skin got created
427  $this->skin->setContext( $this );
428  }
429 
430  return $this->skin;
431  }
432 
442  public function msg( $key, ...$params ) {
443  return wfMessage( $key, ...$params )->setContext( $this );
444  }
445 
451  public static function getMain() {
452  if ( self::$instance === null ) {
453  self::$instance = new self;
454  }
455 
456  return self::$instance;
457  }
458 
467  public static function getMainAndWarn( $func = __METHOD__ ) {
468  wfDebug( $func . ' called without context. ' .
469  "Using RequestContext::getMain() for sanity\n" );
470 
471  return self::getMain();
472  }
473 
477  public static function resetMain() {
478  if ( !( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ) ) ) {
479  throw new MWException( __METHOD__ . '() should be called only from unit tests!' );
480  }
481  self::$instance = null;
482  }
483 
491  public function exportSession() {
493  return [
494  'ip' => $this->getRequest()->getIP(),
495  'headers' => $this->getRequest()->getAllHeaders(),
496  'sessionId' => $session->isPersistent() ? $session->getId() : '',
497  'userId' => $this->getUser()->getId()
498  ];
499  }
500 
523  public static function importScopedSession( array $params ) {
524  if ( strlen( $params['sessionId'] ) &&
525  MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent()
526  ) {
527  // Sanity check to avoid sending random cookies for the wrong users.
528  // This method should only called by CLI scripts or by HTTP job runners.
529  throw new MWException( "Sessions can only be imported when none is active." );
530  } elseif ( !IPUtils::isValid( $params['ip'] ) ) {
531  throw new MWException( "Invalid client IP address '{$params['ip']}'." );
532  }
533 
534  if ( $params['userId'] ) { // logged-in user
535  $user = User::newFromId( $params['userId'] );
536  $user->load();
537  if ( !$user->getId() ) {
538  throw new MWException( "No user with ID '{$params['userId']}'." );
539  }
540  } else { // anon user
541  $user = User::newFromName( $params['ip'], false );
542  }
543 
544  $importSessionFunc = function ( User $user, array $params ) {
545  global $wgRequest, $wgUser;
546 
548 
549  // Commit and close any current session
550  if ( MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
551  session_write_close(); // persist
552  session_id( '' ); // detach
553  $_SESSION = []; // clear in-memory array
554  }
555 
556  // Get new session, if applicable
557  $session = null;
558  if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID
560  $session = $manager->getSessionById( $params['sessionId'], true )
561  ?: $manager->getEmptySession();
562  }
563 
564  // Remove any user IP or agent information, and attach the request
565  // with the new session.
566  $context->setRequest( new FauxRequest( [], false, $session ) );
567  $wgRequest = $context->getRequest(); // b/c
568 
569  // Now that all private information is detached from the user, it should
570  // be safe to load the new user. If errors occur or an exception is thrown
571  // and caught (leaving the main context in a mixed state), there is no risk
572  // of the User object being attached to the wrong IP, headers, or session.
573  $context->setUser( $user );
574  $wgUser = $context->getUser(); // b/c
575  if ( $session && MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
576  session_id( $session->getId() );
577  AtEase::quietCall( 'session_start' );
578  }
579  $request = new FauxRequest( [], false, $session );
580  $request->setIP( $params['ip'] );
581  foreach ( $params['headers'] as $name => $value ) {
582  $request->setHeader( $name, $value );
583  }
584  // Set the current context to use the new WebRequest
585  $context->setRequest( $request );
586  $wgRequest = $context->getRequest(); // b/c
587  };
588 
589  // Stash the old session and load in the new one
590  $oUser = self::getMain()->getUser();
591  $oParams = self::getMain()->exportSession();
592  $oRequest = self::getMain()->getRequest();
593  $importSessionFunc( $user, $params );
594 
595  // Set callback to save and close the new session and reload the old one
596  return new ScopedCallback(
597  function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) {
598  global $wgRequest;
599  $importSessionFunc( $oUser, $oParams );
600  // Restore the exact previous Request object (instead of leaving FauxRequest)
601  RequestContext::getMain()->setRequest( $oRequest );
602  $wgRequest = RequestContext::getMain()->getRequest(); // b/c
603  }
604  );
605  }
606 
621  public static function newExtraneousContext( Title $title, $request = [] ) {
622  $context = new self;
623  $context->setTitle( $title );
624  if ( $request instanceof WebRequest ) {
625  $context->setRequest( $request );
626  } else {
627  $context->setRequest( new FauxRequest( $request ) );
628  }
629  $context->user = User::newFromName( '127.0.0.1', false );
630 
631  return $context;
632  }
633 }
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:323
RequestContext\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: RequestContext.php:442
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:559
RequestContext\getStats
getStats()
Definition: RequestContext.php:140
RequestContext\sanitizeLangCode
static sanitizeLangCode( $code)
Accepts a language code and ensures it's sane.
Definition: RequestContext.php:290
RequestContext\setRequest
setRequest(WebRequest $request)
Definition: RequestContext.php:114
User\getId
getId()
Get the user's ID.
Definition: User.php:2158
RequestContext\$title
Title $title
Definition: RequestContext.php:43
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:137
RequestContext\$user
User $user
Definition: RequestContext.php:58
RequestContext\setWikiPage
setWikiPage(WikiPage $wikiPage)
Definition: RequestContext.php:215
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:694
RequestContext\$instance
static RequestContext $instance
Definition: RequestContext.php:83
RequestContext\setSkin
setSkin(Skin $skin)
Definition: RequestContext.php:387
RequestContext\getRequest
getRequest()
Definition: RequestContext.php:121
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:48
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:535
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1201
RequestContext\newExtraneousContext
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
Definition: RequestContext.php:621
RequestContext\$config
Config $config
Definition: RequestContext.php:78
RequestContext\$skin
Skin $skin
Definition: RequestContext.php:68
$wgTitle
if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Definition: api.php:59
RequestContext\getUser
getUser()
Definition: RequestContext.php:276
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:995
RequestContext\setUser
setUser(User $user)
Definition: RequestContext.php:267
Config
Interface for configuration instances.
Definition: Config.php:28
MWException
MediaWiki exception.
Definition: MWException.php:26
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:143
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:94
Timing
An interface to help developers measure the performance of their applications.
Definition: Timing.php:45
$wgCommandLineMode
global $wgCommandLineMode
Definition: DevelopmentSettings.php:28
RequestContext\getWikiPage
getWikiPage()
Get the WikiPage object.
Definition: RequestContext.php:234
RequestContext\getConfig
getConfig()
Definition: RequestContext.php:101
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:307
MediaWiki
A helper class for throttling authentication attempts.
MediaWiki\Session\SessionManager\singleton
static singleton()
Get the global SessionManager.
Definition: SessionManager.php:91
RequestContext\$lang
Language $lang
Definition: RequestContext.php:63
RequestContext\setLanguage
setLanguage( $language)
Definition: RequestContext.php:313
RequestContext
Group all the pieces relevant to the context of a request into one instance.
Definition: RequestContext.php:34
Title\canExist
canExist()
Can this title represent a page in the wiki's database?
Definition: Title.php:1188
RequestContext\$wikipage
WikiPage $wikipage
Definition: RequestContext.php:48
RequestContext\getTiming
getTiming()
Definition: RequestContext.php:147
RequestContext\getLanguage
getLanguage()
Get the Language object.
Definition: RequestContext.php:332
RequestContext\getMainAndWarn
static getMainAndWarn( $func=__METHOD__)
Get the RequestContext object associated with the main request and gives a warning to the log,...
Definition: RequestContext.php:467
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:913
ContextSource\setContext
setContext(IContextSource $context)
Definition: ContextSource.php:55
WebRequest\getRawVal
getRawVal( $name, $default=null)
Fetch a scalar from the input without normalization, or return $default if it's not set.
Definition: WebRequest.php:463
RequestContext\getSkin
getSkin()
Definition: RequestContext.php:395
MutableContext
Definition: MutableContext.php:25
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
Get the "global" session.
Definition: SessionManager.php:106
RequestContext\resetMain
static resetMain()
Resets singleton returned by getMain().
Definition: RequestContext.php:477
RequestContext\$output
OutputPage $output
Definition: RequestContext.php:53
WebRequest\setIP
setIP( $ip)
Definition: WebRequest.php:1318
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2887
RequestContext\$request
WebRequest $request
Definition: RequestContext.php:38
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:2998
RequestContext\getTitle
getTitle()
Definition: RequestContext.php:168
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:451
RequestContext\importScopedSession
static importScopedSession(array $params)
Import an client IP address, HTTP headers, user ID, and session ID.
Definition: RequestContext.php:523
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:43
$context
$context
Definition: load.php:43
RequestContext\$timing
Timing $timing
Definition: RequestContext.php:73
Title
Represents a title within MediaWiki.
Definition: Title.php:42
RequestContext\$languageRecursion
bool $languageRecursion
Boolean flag to guard against recursion in getLanguage.
Definition: RequestContext.php:89
RequestContext\setOutput
setOutput(OutputPage $output)
Definition: RequestContext.php:249
RequestContext\canUseWikiPage
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
Definition: RequestContext.php:200
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:642
RequestContext\getOutput
getOutput()
Definition: RequestContext.php:256
Skin
The main skin class which provides methods and properties for all other skins.
Definition: Skin.php:38
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
RequestContext\setConfig
setConfig(Config $config)
Definition: RequestContext.php:94
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:39
RequestContext\exportSession
exportSession()
Export the resolved user IP, HTTP headers, user ID, and session ID.
Definition: RequestContext.php:491
RequestContext\hasTitle
hasTitle()
Check, if a Title object is set.
Definition: RequestContext.php:188
RequestContext\setTitle
setTitle(Title $title=null)
Definition: RequestContext.php:159