MediaWiki  master
RequestContext.php
Go to the documentation of this file.
1 <?php
29 use Wikimedia\AtEase\AtEase;
30 use Wikimedia\IPUtils;
31 use Wikimedia\NonSerializable\NonSerializableTrait;
32 use Wikimedia\ScopedCallback;
33 
42  use NonSerializableTrait;
43 
47  private $request;
48 
52  private $title;
53 
57  private $wikipage;
58 
62  private $output;
63 
67  private $user;
68 
72  private $authority;
73 
77  private $lang;
78 
82  private $skin;
83 
87  private $timing;
88 
92  private $config;
93 
97  private static $instance = null;
98 
103  private $languageRecursion = false;
104 
108  public function setConfig( Config $config ) {
109  $this->config = $config;
110  }
111 
115  public function getConfig() {
116  if ( $this->config === null ) {
117  // @todo In the future, we could move this to WebStart.php so
118  // the Config object is ready for when initialization happens
119  $this->config = MediaWikiServices::getInstance()->getMainConfig();
120  }
121 
122  return $this->config;
123  }
124 
128  public function setRequest( WebRequest $request ) {
129  $this->request = $request;
130  }
131 
135  public function getRequest() {
136  if ( $this->request === null ) {
137  global $wgCommandLineMode;
138  // create the WebRequest object on the fly
139  if ( $wgCommandLineMode ) {
140  $this->request = new FauxRequest( [] );
141  } else {
142  $this->request = new WebRequest();
143  }
144  }
145 
146  return $this->request;
147  }
148 
154  public function getStats() {
155  return MediaWikiServices::getInstance()->getStatsdDataFactory();
156  }
157 
161  public function getTiming() {
162  if ( $this->timing === null ) {
163  $this->timing = new Timing( [
164  'logger' => LoggerFactory::getInstance( 'Timing' )
165  ] );
166  }
167  return $this->timing;
168  }
169 
173  public function setTitle( Title $title = null ) {
174  $this->title = $title;
175  // Erase the WikiPage so a new one with the new title gets created.
176  $this->wikipage = null;
177  }
178 
182  public function getTitle() {
183  if ( $this->title === null ) {
184  global $wgTitle; # fallback to $wg till we can improve this
185  $this->title = $wgTitle;
186  $logger = LoggerFactory::getInstance( 'GlobalTitleFail' );
187  $logger->info(
188  __METHOD__ . ' called with no title set.',
189  [ 'exception' => new Exception ]
190  );
191  }
192 
193  return $this->title;
194  }
195 
202  public function hasTitle() {
203  return $this->title !== null;
204  }
205 
214  public function canUseWikiPage() {
215  if ( $this->wikipage ) {
216  // If there's a WikiPage object set, we can for sure get it
217  return true;
218  }
219  // Only pages with legitimate titles can have WikiPages.
220  // That usually means pages in non-virtual namespaces.
221  $title = $this->getTitle();
222  return $title ? $title->canExist() : false;
223  }
224 
229  public function setWikiPage( WikiPage $wikiPage ) {
230  $pageTitle = $wikiPage->getTitle();
231  if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) {
232  $this->setTitle( $pageTitle );
233  }
234  // Defer this to the end since setTitle sets it to null.
235  $this->wikipage = $wikiPage;
236  }
237 
248  public function getWikiPage() {
249  if ( $this->wikipage === null ) {
250  $title = $this->getTitle();
251  if ( $title === null ) {
252  throw new MWException( __METHOD__ . ' called without Title object set' );
253  }
254  $this->wikipage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
255  }
256 
257  return $this->wikipage;
258  }
259 
263  public function setOutput( OutputPage $output ) {
264  $this->output = $output;
265  }
266 
270  public function getOutput() {
271  if ( $this->output === null ) {
272  $this->output = new OutputPage( $this );
273  }
274 
275  return $this->output;
276  }
277 
281  public function setUser( User $user ) {
282  $this->user = $user;
283  // Keep authority consistent
284  $this->authority = $user;
285  // Invalidate cached user interface language
286  $this->lang = null;
287  }
288 
292  public function getUser() {
293  if ( $this->user === null ) {
294  $this->user = User::newFromSession( $this->getRequest() );
295  }
296 
297  return $this->user;
298  }
299 
303  public function setAuthority( Authority $authority ) {
304  $this->authority = $authority;
305  // Keep user consistent
306  $this->user = MediaWikiServices::getInstance()
307  ->getUserFactory()
308  ->newFromAuthority( $authority );
309  // Invalidate cached user interface language
310  $this->lang = null;
311  }
312 
317  public function getAuthority(): Authority {
318  return $this->authority ?: $this->getUser();
319  }
320 
327  public static function sanitizeLangCode( $code ) {
328  global $wgLanguageCode;
329 
330  // BCP 47 - letter case MUST NOT carry meaning
331  $code = strtolower( $code );
332 
333  # Validate $code
334  if ( !$code
335  || !MediaWikiServices::getInstance()->getLanguageNameUtils()
336  ->isValidCode( $code )
337  || $code === 'qqq'
338  ) {
339  $code = $wgLanguageCode;
340  }
341 
342  return $code;
343  }
344 
350  public function setLanguage( $language ) {
351  if ( $language instanceof Language ) {
352  $this->lang = $language;
353  } elseif ( is_string( $language ) ) {
354  $language = self::sanitizeLangCode( $language );
355  $obj = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $language );
356  $this->lang = $obj;
357  } else {
358  throw new MWException( __METHOD__ . " was passed an invalid type of data." );
359  }
360  }
361 
369  public function getLanguage() {
370  if ( $this->languageRecursion === true ) {
371  throw new LogicException( 'Recursion detected' );
372  }
373 
374  if ( $this->lang === null ) {
375  $this->languageRecursion = true;
376 
377  try {
378  $request = $this->getRequest();
379  $user = $this->getUser();
380 
381  // Optimisation: Avoid slow getVal(), this isn't user-generated content.
382  $code = $request->getRawVal( 'uselang', 'user' );
383  if ( $code === 'user' ) {
384  $code = $user->getOption( 'language' );
385  }
386 
387  // There are certain characters we don't allow in language code strings,
388  // but by and large almost any valid UTF-8 string will makes it past
389  // this check and the LanguageNameUtils::isValidCode method it uses.
390  // This is to support on-wiki interface message overrides for
391  // non-existent language codes. Also known as "Uselang hacks".
392  // See <https://www.mediawiki.org/wiki/Manual:Uselang_hack>
393  // For something like "en-whatever" or "de-whatever" it will end up
394  // with a mostly "en" or "de" interface, but with an extra layer of
395  // possible MessageCache overrides from `MediaWiki:*/<code>` titles.
396  // While non-ASCII works here, it is required that they are in
397  // NFC form given this will not convert to normalised form.
398  $code = self::sanitizeLangCode( $code );
399 
400  Hooks::runner()->onUserGetLanguageObject( $user, $code, $this );
401 
402  if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) {
403  $this->lang = MediaWikiServices::getInstance()->getContentLanguage();
404  } else {
405  $obj = MediaWikiServices::getInstance()->getLanguageFactory()
406  ->getLanguage( $code );
407  $this->lang = $obj;
408  }
409  } finally {
410  $this->languageRecursion = false;
411  }
412  }
413 
414  return $this->lang;
415  }
416 
420  public function setSkin( Skin $skin ) {
421  $this->skin = clone $skin;
422  $this->skin->setContext( $this );
423  }
424 
428  public function getSkin() {
429  if ( $this->skin === null ) {
430  $skin = null;
431  Hooks::runner()->onRequestContextCreateSkin( $this, $skin );
432  $factory = MediaWikiServices::getInstance()->getSkinFactory();
433 
434  if ( $skin instanceof Skin ) {
435  // The hook provided a skin object
436  $this->skin = $skin;
437  } elseif ( is_string( $skin ) ) {
438  // The hook provided a skin name
439  // Normalize the key, just in case the hook did something weird.
440  $normalized = Skin::normalizeKey( $skin );
441  $this->skin = $factory->makeSkin( $normalized );
442  } else {
443  // No hook override, go through normal processing
444  if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
445  $userSkin = $this->getUser()->getOption( 'skin' );
446  // Optimisation: Avoid slow getVal(), this isn't user-generated content.
447  $userSkin = $this->getRequest()->getRawVal( 'useskin', $userSkin );
448  } else {
449  $userSkin = $this->getConfig()->get( 'DefaultSkin' );
450  }
451 
452  // Normalize the key in case the user is passing gibberish query params
453  // or has old user preferences (T71566).
454  // Skin::normalizeKey will also validate it, so makeSkin() won't throw.
455  $normalized = Skin::normalizeKey( $userSkin );
456  $this->skin = $factory->makeSkin( $normalized );
457  }
458 
459  // After all that set a context on whatever skin got created
460  $this->skin->setContext( $this );
461  }
462 
463  return $this->skin;
464  }
465 
475  public function msg( $key, ...$params ) {
476  return wfMessage( $key, ...$params )->setContext( $this );
477  }
478 
484  public static function getMain(): RequestContext {
485  if ( self::$instance === null ) {
486  self::$instance = new self;
487  }
488 
489  return self::$instance;
490  }
491 
500  public static function getMainAndWarn( $func = __METHOD__ ) {
501  wfDebug( $func . ' called without context. ' .
502  "Using RequestContext::getMain() for sanity" );
503 
504  return self::getMain();
505  }
506 
510  public static function resetMain() {
511  if ( !( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ) ) ) {
512  throw new MWException( __METHOD__ . '() should be called only from unit tests!' );
513  }
514  self::$instance = null;
515  }
516 
524  public function exportSession() {
526  return [
527  'ip' => $this->getRequest()->getIP(),
528  'headers' => $this->getRequest()->getAllHeaders(),
529  'sessionId' => $session->isPersistent() ? $session->getId() : '',
530  'userId' => $this->getUser()->getId()
531  ];
532  }
533 
534  public function getCsrfTokenSet(): CsrfTokenSet {
535  return new CsrfTokenSet( $this->getRequest() );
536  }
537 
560  public static function importScopedSession( array $params ) {
561  if ( strlen( $params['sessionId'] ) &&
562  MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent()
563  ) {
564  // Sanity check to avoid sending random cookies for the wrong users.
565  // This method should only called by CLI scripts or by HTTP job runners.
566  throw new MWException( "Sessions can only be imported when none is active." );
567  } elseif ( !IPUtils::isValid( $params['ip'] ) ) {
568  throw new MWException( "Invalid client IP address '{$params['ip']}'." );
569  }
570 
571  if ( $params['userId'] ) { // logged-in user
572  $user = User::newFromId( $params['userId'] );
573  $user->load();
574  if ( !$user->getId() ) {
575  throw new MWException( "No user with ID '{$params['userId']}'." );
576  }
577  } else { // anon user
578  $user = User::newFromName( $params['ip'], false );
579  }
580 
581  $importSessionFunc = static function ( User $user, array $params ) {
582  global $wgRequest;
583 
584  $context = RequestContext::getMain();
585 
586  // Commit and close any current session
587  if ( MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
588  session_write_close(); // persist
589  session_id( '' ); // detach
590  $_SESSION = []; // clear in-memory array
591  }
592 
593  // Get new session, if applicable
594  $session = null;
595  if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID
597  $session = $manager->getSessionById( $params['sessionId'], true )
598  ?: $manager->getEmptySession();
599  }
600 
601  // Remove any user IP or agent information, and attach the request
602  // with the new session.
603  $context->setRequest( new FauxRequest( [], false, $session ) );
604  $wgRequest = $context->getRequest(); // b/c
605 
606  // Now that all private information is detached from the user, it should
607  // be safe to load the new user. If errors occur or an exception is thrown
608  // and caught (leaving the main context in a mixed state), there is no risk
609  // of the User object being attached to the wrong IP, headers, or session.
610  $context->setUser( $user );
611  StubGlobalUser::setUser( $context->getUser() ); // b/c
612  if ( $session && MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
613  session_id( $session->getId() );
614  AtEase::quietCall( 'session_start' );
615  }
616  $request = new FauxRequest( [], false, $session );
617  $request->setIP( $params['ip'] );
618  foreach ( $params['headers'] as $name => $value ) {
619  $request->setHeader( $name, $value );
620  }
621  // Set the current context to use the new WebRequest
622  $context->setRequest( $request );
623  $wgRequest = $context->getRequest(); // b/c
624  };
625 
626  // Stash the old session and load in the new one
627  $oUser = self::getMain()->getUser();
628  $oParams = self::getMain()->exportSession();
629  $oRequest = self::getMain()->getRequest();
630  $importSessionFunc( $user, $params );
631 
632  // Set callback to save and close the new session and reload the old one
633  return new ScopedCallback(
634  static function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) {
635  global $wgRequest;
636  $importSessionFunc( $oUser, $oParams );
637  // Restore the exact previous Request object (instead of leaving FauxRequest)
638  RequestContext::getMain()->setRequest( $oRequest );
639  $wgRequest = RequestContext::getMain()->getRequest(); // b/c
640  }
641  );
642  }
643 
658  public static function newExtraneousContext( Title $title, $request = [] ) {
659  $context = new self;
660  $context->setTitle( $title );
661  if ( $request instanceof WebRequest ) {
662  $context->setRequest( $request );
663  } else {
664  $context->setRequest( new FauxRequest( $request ) );
665  }
666  $context->user = User::newFromName( '127.0.0.1', false );
667 
668  return $context;
669  }
670 }
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:378
RequestContext\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: RequestContext.php:475
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:35
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:647
RequestContext\getStats
getStats()
Definition: RequestContext.php:154
RequestContext\sanitizeLangCode
static sanitizeLangCode( $code)
Accepts a language code and ensures it's sane.
Definition: RequestContext.php:327
RequestContext\setRequest
setRequest(WebRequest $request)
Definition: RequestContext.php:128
RequestContext\$authority
Authority $authority
Definition: RequestContext.php:72
RequestContext\getCsrfTokenSet
getCsrfTokenSet()
Get a set of CSRF tokens to obtain and match specific tokens.
Definition: RequestContext.php:534
RequestContext\$title
Title $title
Definition: RequestContext.php:52
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:200
RequestContext\$user
User $user
Definition: RequestContext.php:67
$wgRequest
$wgRequest
Definition: Setup.php:705
RequestContext\setWikiPage
setWikiPage(WikiPage $wikiPage)
Definition: RequestContext.php:229
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:746
RequestContext\setSkin
setSkin(Skin $skin)
Definition: RequestContext.php:420
RequestContext\getRequest
getRequest()
Definition: RequestContext.php:135
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:60
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:606
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1175
RequestContext\newExtraneousContext
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
Definition: RequestContext.php:658
StubGlobalUser\setUser
static setUser( $user)
Reset the stub global user to a different "real" user object, while ensuring that any method calls on...
Definition: StubGlobalUser.php:79
RequestContext\$config
Config $config
Definition: RequestContext.php:92
RequestContext\$skin
Skin $skin
Definition: RequestContext.php:82
RequestContext\getUser
getUser()
Definition: RequestContext.php:292
RequestContext\$instance
static RequestContext null $instance
Definition: RequestContext.php:97
RequestContext\setUser
setUser(User $user)
Definition: RequestContext.php:281
Config
Interface for configuration instances.
Definition: Config.php:30
MWException
MediaWiki exception.
Definition: MWException.php:29
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:134
Timing
An interface to help developers measure the performance of their applications.
Definition: Timing.php:45
$wgCommandLineMode
global $wgCommandLineMode
Definition: DevelopmentSettings.php:29
RequestContext\getWikiPage
getWikiPage()
Get the WikiPage object.
Definition: RequestContext.php:248
RequestContext\getConfig
getConfig()
Definition: RequestContext.php:115
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:311
MediaWiki
A helper class for throttling authentication attempts.
MediaWiki\Session\SessionManager\singleton
static singleton()
Get the global SessionManager.
Definition: SessionManager.php:133
RequestContext\getAuthority
getAuthority()
Definition: RequestContext.php:317
RequestContext\setLanguage
setLanguage( $language)
Definition: RequestContext.php:350
RequestContext
Group all the pieces relevant to the context of a request into one instance @newable.
Definition: RequestContext.php:41
Title\canExist
canExist()
Can this title represent a page in the wiki's database?
Definition: Title.php:1226
RequestContext\$wikipage
WikiPage $wikipage
Definition: RequestContext.php:57
User\getId
getId( $wikiId=self::LOCAL)
Get the user's ID.
Definition: User.php:2088
RequestContext\getTiming
getTiming()
Definition: RequestContext.php:161
RequestContext\getLanguage
getLanguage()
Get the Language object.
Definition: RequestContext.php:369
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:500
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:886
ContextSource\setContext
setContext(IContextSource $context)
Definition: ContextSource.php:63
OutputPage
This is one of the Core classes and should be read at least once by any new developers.
Definition: OutputPage.php:50
WebRequest\getRawVal
getRawVal( $name, $default=null)
Fetch a string WITHOUT any Unicode or line break normalization.
Definition: WebRequest.php:478
$wgTitle
$wgTitle
Definition: Setup.php:852
RequestContext\$lang
Language null $lang
Definition: RequestContext.php:77
RequestContext\getSkin
getSkin()
Definition: RequestContext.php:428
MediaWiki\Permissions\Authority
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
MutableContext
Request-dependent objects containers.
Definition: MutableContext.php:28
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
If PHP's session_id() has been set, returns that session.
Definition: SessionManager.php:146
RequestContext\resetMain
static resetMain()
Resets singleton returned by getMain().
Definition: RequestContext.php:510
RequestContext\$output
OutputPage $output
Definition: RequestContext.php:62
WebRequest\setIP
setIP( $ip)
Definition: WebRequest.php:1369
MediaWiki\Session\CsrfTokenSet
Definition: CsrfTokenSet.php:31
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2561
RequestContext\$request
WebRequest $request
Definition: RequestContext.php:47
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:3443
RequestContext\getTitle
getTitle()
Definition: RequestContext.php:182
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
RequestContext\importScopedSession
static importScopedSession(array $params)
Import an client IP address, HTTP headers, user ID, and session ID.
Definition: RequestContext.php:560
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:58
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:43
RequestContext\$timing
Timing $timing
Definition: RequestContext.php:87
Title
Represents a title within MediaWiki.
Definition: Title.php:47
RequestContext\$languageRecursion
bool $languageRecursion
Boolean flag to guard against recursion in getLanguage.
Definition: RequestContext.php:103
RequestContext\setOutput
setOutput(OutputPage $output)
Definition: RequestContext.php:263
RequestContext\canUseWikiPage
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
Definition: RequestContext.php:214
RequestContext\getOutput
getOutput()
Definition: RequestContext.php:270
Skin
The main skin class which provides methods and properties for all other skins.
Definition: Skin.php:44
RequestContext\setAuthority
setAuthority(Authority $authority)
Definition: RequestContext.php:303
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:68
RequestContext\setConfig
setConfig(Config $config)
Definition: RequestContext.php:108
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:42
RequestContext\exportSession
exportSession()
Export the resolved user IP, HTTP headers, user ID, and session ID.
Definition: RequestContext.php:524
RequestContext\hasTitle
hasTitle()
Check, if a Title object is set.
Definition: RequestContext.php:202
RequestContext\setTitle
setTitle(Title $title=null)
Definition: RequestContext.php:173