[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Request-dependant objects containers. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @since 1.18 21 * 22 * @author Alexandre Emsenhuber 23 * @author Daniel Friesen 24 * @file 25 */ 26 27 /** 28 * Group all the pieces relevant to the context of a request into one instance 29 */ 30 class RequestContext implements IContextSource { 31 /** 32 * @var WebRequest 33 */ 34 private $request; 35 36 /** 37 * @var Title 38 */ 39 private $title; 40 41 /** 42 * @var WikiPage 43 */ 44 private $wikipage; 45 46 /** 47 * @var OutputPage 48 */ 49 private $output; 50 51 /** 52 * @var User 53 */ 54 private $user; 55 56 /** 57 * @var Language 58 */ 59 private $lang; 60 61 /** 62 * @var Skin 63 */ 64 private $skin; 65 66 /** 67 * @var Config 68 */ 69 private $config; 70 71 /** 72 * @var RequestContext 73 */ 74 private static $instance = null; 75 76 /** 77 * Set the Config object 78 * 79 * @param Config $c 80 */ 81 public function setConfig( Config $c ) { 82 $this->config = $c; 83 } 84 85 /** 86 * Get the Config object 87 * 88 * @return Config 89 */ 90 public function getConfig() { 91 if ( $this->config === null ) { 92 // @todo In the future, we could move this to WebStart.php so 93 // the Config object is ready for when initialization happens 94 $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); 95 } 96 97 return $this->config; 98 } 99 100 /** 101 * Set the WebRequest object 102 * 103 * @param WebRequest $r 104 */ 105 public function setRequest( WebRequest $r ) { 106 $this->request = $r; 107 } 108 109 /** 110 * Get the WebRequest object 111 * 112 * @return WebRequest 113 */ 114 public function getRequest() { 115 if ( $this->request === null ) { 116 global $wgRequest; # fallback to $wg till we can improve this 117 $this->request = $wgRequest; 118 } 119 120 return $this->request; 121 } 122 123 /** 124 * Set the Title object 125 * 126 * @param Title $title 127 */ 128 public function setTitle( Title $title ) { 129 $this->title = $title; 130 // Erase the WikiPage so a new one with the new title gets created. 131 $this->wikipage = null; 132 } 133 134 /** 135 * Get the Title object 136 * 137 * @return Title|null 138 */ 139 public function getTitle() { 140 if ( $this->title === null ) { 141 global $wgTitle; # fallback to $wg till we can improve this 142 $this->title = $wgTitle; 143 } 144 145 return $this->title; 146 } 147 148 /** 149 * Check whether a WikiPage object can be get with getWikiPage(). 150 * Callers should expect that an exception is thrown from getWikiPage() 151 * if this method returns false. 152 * 153 * @since 1.19 154 * @return bool 155 */ 156 public function canUseWikiPage() { 157 if ( $this->wikipage ) { 158 // If there's a WikiPage object set, we can for sure get it 159 return true; 160 } 161 // Only pages with legitimate titles can have WikiPages. 162 // That usually means pages in non-virtual namespaces. 163 $title = $this->getTitle(); 164 return $title ? $title->canExist() : false; 165 } 166 167 /** 168 * Set the WikiPage object 169 * 170 * @since 1.19 171 * @param WikiPage $p 172 */ 173 public function setWikiPage( WikiPage $p ) { 174 $contextTitle = $this->getTitle(); 175 $pageTitle = $p->getTitle(); 176 if ( !$contextTitle || !$pageTitle->equals( $contextTitle ) ) { 177 $this->setTitle( $pageTitle ); 178 } 179 // Defer this to the end since setTitle sets it to null. 180 $this->wikipage = $p; 181 } 182 183 /** 184 * Get the WikiPage object. 185 * May throw an exception if there's no Title object set or the Title object 186 * belongs to a special namespace that doesn't have WikiPage, so use first 187 * canUseWikiPage() to check whether this method can be called safely. 188 * 189 * @since 1.19 190 * @throws MWException 191 * @return WikiPage 192 */ 193 public function getWikiPage() { 194 if ( $this->wikipage === null ) { 195 $title = $this->getTitle(); 196 if ( $title === null ) { 197 throw new MWException( __METHOD__ . ' called without Title object set' ); 198 } 199 $this->wikipage = WikiPage::factory( $title ); 200 } 201 202 return $this->wikipage; 203 } 204 205 /** 206 * @param OutputPage $o 207 */ 208 public function setOutput( OutputPage $o ) { 209 $this->output = $o; 210 } 211 212 /** 213 * Get the OutputPage object 214 * 215 * @return OutputPage 216 */ 217 public function getOutput() { 218 if ( $this->output === null ) { 219 $this->output = new OutputPage( $this ); 220 } 221 222 return $this->output; 223 } 224 225 /** 226 * Set the User object 227 * 228 * @param User $u 229 */ 230 public function setUser( User $u ) { 231 $this->user = $u; 232 } 233 234 /** 235 * Get the User object 236 * 237 * @return User 238 */ 239 public function getUser() { 240 if ( $this->user === null ) { 241 $this->user = User::newFromSession( $this->getRequest() ); 242 } 243 244 return $this->user; 245 } 246 247 /** 248 * Accepts a language code and ensures it's sane. Outputs a cleaned up language 249 * code and replaces with $wgLanguageCode if not sane. 250 * @param string $code Language code 251 * @return string 252 */ 253 public static function sanitizeLangCode( $code ) { 254 global $wgLanguageCode; 255 256 // BCP 47 - letter case MUST NOT carry meaning 257 $code = strtolower( $code ); 258 259 # Validate $code 260 if ( !$code || !Language::isValidCode( $code ) || $code === 'qqq' ) { 261 wfDebug( "Invalid user language code\n" ); 262 $code = $wgLanguageCode; 263 } 264 265 return $code; 266 } 267 268 /** 269 * Set the Language object 270 * 271 * @param Language|string $l Language instance or language code 272 * @throws MWException 273 * @since 1.19 274 */ 275 public function setLanguage( $l ) { 276 if ( $l instanceof Language ) { 277 $this->lang = $l; 278 } elseif ( is_string( $l ) ) { 279 $l = self::sanitizeLangCode( $l ); 280 $obj = Language::factory( $l ); 281 $this->lang = $obj; 282 } else { 283 throw new MWException( __METHOD__ . " was passed an invalid type of data." ); 284 } 285 } 286 287 /** 288 * Get the Language object. 289 * Initialization of user or request objects can depend on this. 290 * 291 * @return Language 292 * @since 1.19 293 */ 294 public function getLanguage() { 295 if ( isset( $this->recursion ) ) { 296 trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING ); 297 $e = new Exception; 298 wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() ); 299 300 $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en'; 301 $this->lang = Language::factory( $code ); 302 } elseif ( $this->lang === null ) { 303 $this->recursion = true; 304 305 global $wgContLang; 306 307 try { 308 $request = $this->getRequest(); 309 $user = $this->getUser(); 310 311 $code = $request->getVal( 'uselang', $user->getOption( 'language' ) ); 312 $code = self::sanitizeLangCode( $code ); 313 314 wfRunHooks( 'UserGetLanguageObject', array( $user, &$code, $this ) ); 315 316 if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) { 317 $this->lang = $wgContLang; 318 } else { 319 $obj = Language::factory( $code ); 320 $this->lang = $obj; 321 } 322 323 unset( $this->recursion ); 324 } 325 catch ( Exception $ex ) { 326 unset( $this->recursion ); 327 throw $ex; 328 } 329 } 330 331 return $this->lang; 332 } 333 334 /** 335 * Set the Skin object 336 * 337 * @param Skin $s 338 */ 339 public function setSkin( Skin $s ) { 340 $this->skin = clone $s; 341 $this->skin->setContext( $this ); 342 } 343 344 /** 345 * Get the Skin object 346 * 347 * @return Skin 348 */ 349 public function getSkin() { 350 if ( $this->skin === null ) { 351 wfProfileIn( __METHOD__ . '-createskin' ); 352 353 $skin = null; 354 wfRunHooks( 'RequestContextCreateSkin', array( $this, &$skin ) ); 355 $factory = SkinFactory::getDefaultInstance(); 356 357 // If the hook worked try to set a skin from it 358 if ( $skin instanceof Skin ) { 359 $this->skin = $skin; 360 } elseif ( is_string( $skin ) ) { 361 // Normalize the key, just in case the hook did something weird. 362 $normalized = Skin::normalizeKey( $skin ); 363 $this->skin = $factory->makeSkin( $normalized ); 364 } 365 366 // If this is still null (the hook didn't run or didn't work) 367 // then go through the normal processing to load a skin 368 if ( $this->skin === null ) { 369 if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) { 370 # get the user skin 371 $userSkin = $this->getUser()->getOption( 'skin' ); 372 $userSkin = $this->getRequest()->getVal( 'useskin', $userSkin ); 373 } else { 374 # if we're not allowing users to override, then use the default 375 $userSkin = $this->getConfig()->get( 'DefaultSkin' ); 376 } 377 378 // Normalize the key in case the user is passing gibberish 379 // or has old preferences (bug 69566). 380 $normalized = Skin::normalizeKey( $userSkin ); 381 382 // Skin::normalizeKey will also validate it, so 383 // this won't throw an exception 384 $this->skin = $factory->makeSkin( $normalized ); 385 } 386 387 // After all that set a context on whatever skin got created 388 $this->skin->setContext( $this ); 389 wfProfileOut( __METHOD__ . '-createskin' ); 390 } 391 392 return $this->skin; 393 } 394 395 /** Helpful methods **/ 396 397 /** 398 * Get a Message object with context set 399 * Parameters are the same as wfMessage() 400 * 401 * @return Message 402 */ 403 public function msg() { 404 $args = func_get_args(); 405 406 return call_user_func_array( 'wfMessage', $args )->setContext( $this ); 407 } 408 409 /** Static methods **/ 410 411 /** 412 * Get the RequestContext object associated with the main request 413 * 414 * @return RequestContext 415 */ 416 public static function getMain() { 417 if ( self::$instance === null ) { 418 self::$instance = new self; 419 } 420 421 return self::$instance; 422 } 423 424 /** 425 * Get the RequestContext object associated with the main request 426 * and gives a warning to the log, to find places, where a context maybe is missing. 427 * 428 * @param string $func 429 * @return RequestContext 430 * @since 1.24 431 */ 432 public static function getMainAndWarn( $func = __METHOD__ ) { 433 wfDebug( $func . ' called without context. ' . 434 "Using RequestContext::getMain() for sanity\n" ); 435 436 return self::getMain(); 437 } 438 439 /** 440 * Resets singleton returned by getMain(). Should be called only from unit tests. 441 */ 442 public static function resetMain() { 443 if ( !defined( 'MW_PHPUNIT_TEST' ) ) { 444 throw new MWException( __METHOD__ . '() should be called only from unit tests!' ); 445 } 446 self::$instance = null; 447 } 448 449 /** 450 * Export the resolved user IP, HTTP headers, user ID, and session ID. 451 * The result will be reasonably sized to allow for serialization. 452 * 453 * @return array 454 * @since 1.21 455 */ 456 public function exportSession() { 457 return array( 458 'ip' => $this->getRequest()->getIP(), 459 'headers' => $this->getRequest()->getAllHeaders(), 460 'sessionId' => session_id(), 461 'userId' => $this->getUser()->getId() 462 ); 463 } 464 465 /** 466 * Import the resolved user IP, HTTP headers, user ID, and session ID. 467 * This sets the current session and sets $wgUser and $wgRequest. 468 * Once the return value falls out of scope, the old context is restored. 469 * This function can only be called within CLI mode scripts. 470 * 471 * This will setup the session from the given ID. This is useful when 472 * background scripts inherit context when acting on behalf of a user. 473 * 474 * @note suhosin.session.encrypt may interfere with this method. 475 * 476 * @param array $params Result of RequestContext::exportSession() 477 * @return ScopedCallback 478 * @throws MWException 479 * @since 1.21 480 */ 481 public static function importScopedSession( array $params ) { 482 if ( PHP_SAPI !== 'cli' ) { 483 // Don't send random private cookies or turn $wgRequest into FauxRequest 484 throw new MWException( "Sessions can only be imported in cli mode." ); 485 } elseif ( !strlen( $params['sessionId'] ) ) { 486 throw new MWException( "No session ID was specified." ); 487 } 488 489 if ( $params['userId'] ) { // logged-in user 490 $user = User::newFromId( $params['userId'] ); 491 $user->load(); 492 if ( !$user->getId() ) { 493 throw new MWException( "No user with ID '{$params['userId']}'." ); 494 } 495 } elseif ( !IP::isValid( $params['ip'] ) ) { 496 throw new MWException( "Could not load user '{$params['ip']}'." ); 497 } else { // anon user 498 $user = User::newFromName( $params['ip'], false ); 499 } 500 501 $importSessionFunction = function ( User $user, array $params ) { 502 global $wgRequest, $wgUser; 503 504 $context = RequestContext::getMain(); 505 // Commit and close any current session 506 session_write_close(); // persist 507 session_id( '' ); // detach 508 $_SESSION = array(); // clear in-memory array 509 // Remove any user IP or agent information 510 $context->setRequest( new FauxRequest() ); 511 $wgRequest = $context->getRequest(); // b/c 512 // Now that all private information is detached from the user, it should 513 // be safe to load the new user. If errors occur or an exception is thrown 514 // and caught (leaving the main context in a mixed state), there is no risk 515 // of the User object being attached to the wrong IP, headers, or session. 516 $context->setUser( $user ); 517 $wgUser = $context->getUser(); // b/c 518 if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID 519 wfSetupSession( $params['sessionId'] ); // sets $_SESSION 520 } 521 $request = new FauxRequest( array(), false, $_SESSION ); 522 $request->setIP( $params['ip'] ); 523 foreach ( $params['headers'] as $name => $value ) { 524 $request->setHeader( $name, $value ); 525 } 526 // Set the current context to use the new WebRequest 527 $context->setRequest( $request ); 528 $wgRequest = $context->getRequest(); // b/c 529 }; 530 531 // Stash the old session and load in the new one 532 $oUser = self::getMain()->getUser(); 533 $oParams = self::getMain()->exportSession(); 534 $importSessionFunction( $user, $params ); 535 536 // Set callback to save and close the new session and reload the old one 537 return new ScopedCallback( function () use ( $importSessionFunction, $oUser, $oParams ) { 538 $importSessionFunction( $oUser, $oParams ); 539 } ); 540 } 541 542 /** 543 * Create a new extraneous context. The context is filled with information 544 * external to the current session. 545 * - Title is specified by argument 546 * - Request is a FauxRequest, or a FauxRequest can be specified by argument 547 * - User is an anonymous user, for separation IPv4 localhost is used 548 * - Language will be based on the anonymous user and request, may be content 549 * language or a uselang param in the fauxrequest data may change the lang 550 * - Skin will be based on the anonymous user, should be the wiki's default skin 551 * 552 * @param Title $title Title to use for the extraneous request 553 * @param WebRequest|array $request A WebRequest or data to use for a FauxRequest 554 * @return RequestContext 555 */ 556 public static function newExtraneousContext( Title $title, $request = array() ) { 557 $context = new self; 558 $context->setTitle( $title ); 559 if ( $request instanceof WebRequest ) { 560 $context->setRequest( $request ); 561 } else { 562 $context->setRequest( new FauxRequest( $request ) ); 563 } 564 $context->user = User::newFromName( '127.0.0.1', false ); 565 566 return $context; 567 } 568 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |