MediaWiki  master
RequestContext.php
Go to the documentation of this file.
1 <?php
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;
171  wfDebugLog(
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() {
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
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 }
getRawVal( $name, $default=null)
Fetch a scalar from the input without normalization, or return $default if it&#39;s not set...
Definition: WebRequest.php:465
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
setContext(IContextSource $context)
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:104
$context
Definition: load.php:45
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:2916
A helper class for throttling authentication attempts.
exportSession()
Export the resolved user IP, HTTP headers, user ID, and session ID.
static importScopedSession(array $params)
Import an client IP address, HTTP headers, user ID, and session ID.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
setSkin(Skin $skin)
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
$wgLanguageCode
Site language code.
static getMain()
Get the RequestContext object associated with the main request.
Interface for configuration instances.
Definition: Config.php:28
canExist()
Can this title represent a page in the wiki&#39;s database?
Definition: Title.php:1194
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
static isValid( $ip)
Validate an IP address.
Definition: IP.php:111
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:307
setWikiPage(WikiPage $wikiPage)
bool $languageRecursion
Boolean flag to guard against recursion in getLanguage.
getTitle()
Get the title object of the article.
Definition: WikiPage.php:298
static resetMain()
Resets singleton returned by getMain().
OutputPage $output
static newExtraneousContext(Title $title, $request=[])
Create a new extraneous context.
hasTitle()
Check, if a Title object is set.
setTitle(Title $title=null)
An interface to help developers measure the performance of their applications.
Definition: Timing.php:45
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:212
getWikiPage()
Get the WikiPage object.
static singleton()
Get the global SessionManager.
setUser(User $user)
static getGlobalSession()
Get the "global" session.
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:543
getId()
Get the user&#39;s ID.
Definition: User.php:2201
WikiPage $wikipage
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
static sanitizeLangCode( $code)
Accepts a language code and ensures it&#39;s sane.
global $wgCommandLineMode
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:678
setLanguage( $language)
static getMainAndWarn( $func=__METHOD__)
Get the RequestContext object associated with the main request and gives a warning to the log...
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:727
WebRequest $request
setRequest(WebRequest $request)
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Definition: api.php:58
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:519
setOutput(OutputPage $output)
static isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:379
getLanguage()
Get the Language object.
static RequestContext $instance
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
setConfig(Config $config)