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\NonSerializable\NonSerializableTrait;
30 use Wikimedia\ScopedCallback;
31 
40  use NonSerializableTrait;
41 
45  private $request;
46 
50  private $title;
51 
55  private $wikipage;
56 
60  private $output;
61 
65  private $user;
66 
70  private $lang;
71 
75  private $skin;
76 
80  private $timing;
81 
85  private $config;
86 
90  private static $instance = null;
91 
96  private $languageRecursion = false;
97 
101  public function setConfig( Config $config ) {
102  $this->config = $config;
103  }
104 
108  public function getConfig() {
109  if ( $this->config === null ) {
110  // @todo In the future, we could move this to WebStart.php so
111  // the Config object is ready for when initialization happens
112  $this->config = MediaWikiServices::getInstance()->getMainConfig();
113  }
114 
115  return $this->config;
116  }
117 
121  public function setRequest( WebRequest $request ) {
122  $this->request = $request;
123  }
124 
128  public function getRequest() {
129  if ( $this->request === null ) {
130  global $wgCommandLineMode;
131  // create the WebRequest object on the fly
132  if ( $wgCommandLineMode ) {
133  $this->request = new FauxRequest( [] );
134  } else {
135  $this->request = new WebRequest();
136  }
137  }
138 
139  return $this->request;
140  }
141 
147  public function getStats() {
148  return MediaWikiServices::getInstance()->getStatsdDataFactory();
149  }
150 
154  public function getTiming() {
155  if ( $this->timing === null ) {
156  $this->timing = new Timing( [
157  'logger' => LoggerFactory::getInstance( 'Timing' )
158  ] );
159  }
160  return $this->timing;
161  }
162 
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 
175  public function getTitle() {
176  if ( $this->title === null ) {
177  global $wgTitle; # fallback to $wg till we can improve this
178  $this->title = $wgTitle;
179  $logger = LoggerFactory::getInstance( 'GlobalTitleFail' );
180  $logger->info(
181  __METHOD__ . ' called with no title set.',
182  [ 'exception' => new Exception ]
183  );
184  }
185 
186  return $this->title;
187  }
188 
195  public function hasTitle() {
196  return $this->title !== null;
197  }
198 
207  public function canUseWikiPage() {
208  if ( $this->wikipage ) {
209  // If there's a WikiPage object set, we can for sure get it
210  return true;
211  }
212  // Only pages with legitimate titles can have WikiPages.
213  // That usually means pages in non-virtual namespaces.
214  $title = $this->getTitle();
215  return $title ? $title->canExist() : false;
216  }
217 
222  public function setWikiPage( WikiPage $wikiPage ) {
223  $pageTitle = $wikiPage->getTitle();
224  if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) {
225  $this->setTitle( $pageTitle );
226  }
227  // Defer this to the end since setTitle sets it to null.
228  $this->wikipage = $wikiPage;
229  }
230 
241  public function getWikiPage() {
242  if ( $this->wikipage === null ) {
243  $title = $this->getTitle();
244  if ( $title === null ) {
245  throw new MWException( __METHOD__ . ' called without Title object set' );
246  }
247  $this->wikipage = WikiPage::factory( $title );
248  }
249 
250  return $this->wikipage;
251  }
252 
256  public function setOutput( OutputPage $output ) {
257  $this->output = $output;
258  }
259 
263  public function getOutput() {
264  if ( $this->output === null ) {
265  $this->output = new OutputPage( $this );
266  }
267 
268  return $this->output;
269  }
270 
274  public function setUser( User $user ) {
275  $this->user = $user;
276  // Invalidate cached user interface language
277  $this->lang = null;
278  }
279 
283  public function getUser() {
284  if ( $this->user === null ) {
285  $this->user = User::newFromSession( $this->getRequest() );
286  }
287 
288  return $this->user;
289  }
290 
297  public static function sanitizeLangCode( $code ) {
298  global $wgLanguageCode;
299 
300  // BCP 47 - letter case MUST NOT carry meaning
301  $code = strtolower( $code );
302 
303  # Validate $code
304  if ( !$code
305  || !MediaWikiServices::getInstance()->getLanguageNameUtils()
306  ->isValidCode( $code )
307  || $code === 'qqq'
308  ) {
309  $code = $wgLanguageCode;
310  }
311 
312  return $code;
313  }
314 
320  public function setLanguage( $language ) {
321  if ( $language instanceof Language ) {
322  $this->lang = $language;
323  } elseif ( is_string( $language ) ) {
324  $language = self::sanitizeLangCode( $language );
325  $obj = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $language );
326  $this->lang = $obj;
327  } else {
328  throw new MWException( __METHOD__ . " was passed an invalid type of data." );
329  }
330  }
331 
339  public function getLanguage() {
340  if ( $this->languageRecursion === true ) {
341  throw new LogicException( 'Recursion detected' );
342  }
343 
344  if ( $this->lang === null ) {
345  $this->languageRecursion = true;
346 
347  try {
348  $request = $this->getRequest();
349  $user = $this->getUser();
350 
351  // Optimisation: Avoid slow getVal(), this isn't user-generated content.
352  $code = $request->getRawVal( 'uselang', 'user' );
353  if ( $code === 'user' ) {
354  $code = $user->getOption( 'language' );
355  }
356 
357  // There are certain characters we don't allow in language code strings,
358  // but by and large almost any valid UTF-8 string will makes it past
359  // this check and the LanguageNameUtils::isValidCode method it uses.
360  // This is to support on-wiki interface message overrides for
361  // non-existent language codes. Also known as "Uselang hacks".
362  // See <https://www.mediawiki.org/wiki/Manual:Uselang_hack>
363  // For something like "en-whatever" or "de-whatever" it will end up
364  // with a mostly "en" or "de" interface, but with an extra layer of
365  // possible MessageCache overrides from `MediaWiki:*/<code>` titles.
366  // While non-ASCII works here, it is required that they are in
367  // NFC form given this will not convert to normalised form.
368  $code = self::sanitizeLangCode( $code );
369 
370  Hooks::runner()->onUserGetLanguageObject( $user, $code, $this );
371 
372  if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) {
373  $this->lang = MediaWikiServices::getInstance()->getContentLanguage();
374  } else {
375  $obj = MediaWikiServices::getInstance()->getLanguageFactory()
376  ->getLanguage( $code );
377  $this->lang = $obj;
378  }
379  } finally {
380  $this->languageRecursion = false;
381  }
382  }
383 
384  return $this->lang;
385  }
386 
390  public function setSkin( Skin $skin ) {
391  $this->skin = clone $skin;
392  $this->skin->setContext( $this );
393  }
394 
398  public function getSkin() {
399  if ( $this->skin === null ) {
400  $skin = null;
401  Hooks::runner()->onRequestContextCreateSkin( $this, $skin );
402  $factory = MediaWikiServices::getInstance()->getSkinFactory();
403 
404  if ( $skin instanceof Skin ) {
405  // The hook provided a skin object
406  $this->skin = $skin;
407  } elseif ( is_string( $skin ) ) {
408  // The hook provided a skin name
409  // Normalize the key, just in case the hook did something weird.
410  $normalized = Skin::normalizeKey( $skin );
411  $this->skin = $factory->makeSkin( $normalized );
412  } else {
413  // No hook override, go through normal processing
414  if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
415  $userSkin = $this->getUser()->getOption( 'skin' );
416  // Optimisation: Avoid slow getVal(), this isn't user-generated content.
417  $userSkin = $this->getRequest()->getRawVal( 'useskin', $userSkin );
418  } else {
419  $userSkin = $this->getConfig()->get( 'DefaultSkin' );
420  }
421 
422  // Normalize the key in case the user is passing gibberish query params
423  // or has old user preferences (T71566).
424  // Skin::normalizeKey will also validate it, so makeSkin() won't throw.
425  $normalized = Skin::normalizeKey( $userSkin );
426  $this->skin = $factory->makeSkin( $normalized );
427  }
428 
429  // After all that set a context on whatever skin got created
430  $this->skin->setContext( $this );
431  }
432 
433  return $this->skin;
434  }
435 
445  public function msg( $key, ...$params ) {
446  return wfMessage( $key, ...$params )->setContext( $this );
447  }
448 
454  public static function getMain() {
455  if ( self::$instance === null ) {
456  self::$instance = new self;
457  }
458 
459  return self::$instance;
460  }
461 
470  public static function getMainAndWarn( $func = __METHOD__ ) {
471  wfDebug( $func . ' called without context. ' .
472  "Using RequestContext::getMain() for sanity" );
473 
474  return self::getMain();
475  }
476 
480  public static function resetMain() {
481  if ( !( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ) ) ) {
482  throw new MWException( __METHOD__ . '() should be called only from unit tests!' );
483  }
484  self::$instance = null;
485  }
486 
494  public function exportSession() {
496  return [
497  'ip' => $this->getRequest()->getIP(),
498  'headers' => $this->getRequest()->getAllHeaders(),
499  'sessionId' => $session->isPersistent() ? $session->getId() : '',
500  'userId' => $this->getUser()->getId()
501  ];
502  }
503 
526  public static function importScopedSession( array $params ) {
527  if ( strlen( $params['sessionId'] ) &&
528  MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent()
529  ) {
530  // Sanity check to avoid sending random cookies for the wrong users.
531  // This method should only called by CLI scripts or by HTTP job runners.
532  throw new MWException( "Sessions can only be imported when none is active." );
533  } elseif ( !IPUtils::isValid( $params['ip'] ) ) {
534  throw new MWException( "Invalid client IP address '{$params['ip']}'." );
535  }
536 
537  if ( $params['userId'] ) { // logged-in user
538  $user = User::newFromId( $params['userId'] );
539  $user->load();
540  if ( !$user->getId() ) {
541  throw new MWException( "No user with ID '{$params['userId']}'." );
542  }
543  } else { // anon user
544  $user = User::newFromName( $params['ip'], false );
545  }
546 
547  $importSessionFunc = function ( User $user, array $params ) {
548  global $wgRequest, $wgUser;
549 
550  $context = RequestContext::getMain();
551 
552  // Commit and close any current session
553  if ( MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
554  session_write_close(); // persist
555  session_id( '' ); // detach
556  $_SESSION = []; // clear in-memory array
557  }
558 
559  // Get new session, if applicable
560  $session = null;
561  if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID
563  $session = $manager->getSessionById( $params['sessionId'], true )
564  ?: $manager->getEmptySession();
565  }
566 
567  // Remove any user IP or agent information, and attach the request
568  // with the new session.
569  $context->setRequest( new FauxRequest( [], false, $session ) );
570  $wgRequest = $context->getRequest(); // b/c
571 
572  // Now that all private information is detached from the user, it should
573  // be safe to load the new user. If errors occur or an exception is thrown
574  // and caught (leaving the main context in a mixed state), there is no risk
575  // of the User object being attached to the wrong IP, headers, or session.
576  $context->setUser( $user );
577  $wgUser = $context->getUser(); // b/c
578  if ( $session && MediaWiki\Session\PHPSessionHandler::isEnabled() ) {
579  session_id( $session->getId() );
580  AtEase::quietCall( 'session_start' );
581  }
582  $request = new FauxRequest( [], false, $session );
583  $request->setIP( $params['ip'] );
584  foreach ( $params['headers'] as $name => $value ) {
585  $request->setHeader( $name, $value );
586  }
587  // Set the current context to use the new WebRequest
588  $context->setRequest( $request );
589  $wgRequest = $context->getRequest(); // b/c
590  };
591 
592  // Stash the old session and load in the new one
593  $oUser = self::getMain()->getUser();
594  $oParams = self::getMain()->exportSession();
595  $oRequest = self::getMain()->getRequest();
596  $importSessionFunc( $user, $params );
597 
598  // Set callback to save and close the new session and reload the old one
599  return new ScopedCallback(
600  function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) {
601  global $wgRequest;
602  $importSessionFunc( $oUser, $oParams );
603  // Restore the exact previous Request object (instead of leaving FauxRequest)
604  RequestContext::getMain()->setRequest( $oRequest );
605  $wgRequest = RequestContext::getMain()->getRequest(); // b/c
606  }
607  );
608  }
609 
624  public static function newExtraneousContext( Title $title, $request = [] ) {
625  $context = new self;
626  $context->setTitle( $title );
627  if ( $request instanceof WebRequest ) {
628  $context->setRequest( $request );
629  } else {
630  $context->setRequest( new FauxRequest( $request ) );
631  }
632  $context->user = User::newFromName( '127.0.0.1', false );
633 
634  return $context;
635  }
636 }
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:322
RequestContext\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: RequestContext.php:445
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:579
RequestContext\getStats
getStats()
Definition: RequestContext.php:147
RequestContext\sanitizeLangCode
static sanitizeLangCode( $code)
Accepts a language code and ensures it's sane.
Definition: RequestContext.php:297
RequestContext\setRequest
setRequest(WebRequest $request)
Definition: RequestContext.php:121
User\getId
getId()
Get the user's ID.
Definition: User.php:2033
RequestContext\$title
Title $title
Definition: RequestContext.php:50
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:160
RequestContext\$user
User $user
Definition: RequestContext.php:65
RequestContext\setWikiPage
setWikiPage(WikiPage $wikiPage)
Definition: RequestContext.php:222
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:709
RequestContext\$instance
static RequestContext $instance
Definition: RequestContext.php:90
RequestContext\setSkin
setSkin(Skin $skin)
Definition: RequestContext.php:390
RequestContext\getRequest
getRequest()
Definition: RequestContext.php:128
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:54
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:542
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1220
RequestContext\newExtraneousContext
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
Definition: RequestContext.php:624
RequestContext\$config
Config $config
Definition: RequestContext.php:85
RequestContext\$skin
Skin $skin
Definition: RequestContext.php:75
$wgTitle
$wgTitle
Definition: Setup.php:794
RequestContext\getUser
getUser()
Definition: RequestContext.php:283
RequestContext\setUser
setUser(User $user)
Definition: RequestContext.php:274
Config
Interface for configuration instances.
Definition: Config.php:30
MWException
MediaWiki exception.
Definition: MWException.php:29
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:159
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:101
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:241
RequestContext\getConfig
getConfig()
Definition: RequestContext.php:108
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:284
MediaWiki
A helper class for throttling authentication attempts.
MediaWiki\Session\SessionManager\singleton
static singleton()
Get the global SessionManager.
Definition: SessionManager.php:99
RequestContext\$lang
Language $lang
Definition: RequestContext.php:70
RequestContext\setLanguage
setLanguage( $language)
Definition: RequestContext.php:320
RequestContext
Group all the pieces relevant to the context of a request into one instance @newable.
Definition: RequestContext.php:39
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:55
RequestContext\getTiming
getTiming()
Definition: RequestContext.php:154
RequestContext\getLanguage
getLanguage()
Get the Language object.
Definition: RequestContext.php:339
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:470
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:910
ContextSource\setContext
setContext(IContextSource $context)
Definition: ContextSource.php:61
OutputPage
This is one of the Core classes and should be read at least once by any new developers.
Definition: OutputPage.php:47
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:479
RequestContext\getSkin
getSkin()
Definition: RequestContext.php:398
MutableContext
Definition: MutableContext.php:25
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
Get the "global" session.
Definition: SessionManager.php:114
RequestContext\resetMain
static resetMain()
Resets singleton returned by getMain().
Definition: RequestContext.php:480
RequestContext\$output
OutputPage $output
Definition: RequestContext.php:60
WebRequest\setIP
setIP( $ip)
Definition: WebRequest.php:1342
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2577
RequestContext\$request
WebRequest $request
Definition: RequestContext.php:45
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:3093
RequestContext\getTitle
getTitle()
Definition: RequestContext.php:175
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:454
RequestContext\importScopedSession
static importScopedSession(array $params)
Import an client IP address, HTTP headers, user ID, and session ID.
Definition: RequestContext.php:526
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:42
RequestContext\$timing
Timing $timing
Definition: RequestContext.php:80
Title
Represents a title within MediaWiki.
Definition: Title.php:41
RequestContext\$languageRecursion
bool $languageRecursion
Boolean flag to guard against recursion in getLanguage.
Definition: RequestContext.php:96
RequestContext\setOutput
setOutput(OutputPage $output)
Definition: RequestContext.php:256
RequestContext\canUseWikiPage
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
Definition: RequestContext.php:207
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:644
RequestContext\getOutput
getOutput()
Definition: RequestContext.php:263
Skin
The main skin class which provides methods and properties for all other skins.
Definition: Skin.php:42
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
RequestContext\setConfig
setConfig(Config $config)
Definition: RequestContext.php:101
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:41
RequestContext\exportSession
exportSession()
Export the resolved user IP, HTTP headers, user ID, and session ID.
Definition: RequestContext.php:494
RequestContext\hasTitle
hasTitle()
Check, if a Title object is set.
Definition: RequestContext.php:195
RequestContext\setTitle
setTitle(Title $title=null)
Definition: RequestContext.php:166