MediaWiki  REL1_24
RequestContext.php
Go to the documentation of this file.
00001 <?php
00030 class RequestContext implements IContextSource {
00034     private $request;
00035 
00039     private $title;
00040 
00044     private $wikipage;
00045 
00049     private $output;
00050 
00054     private $user;
00055 
00059     private $lang;
00060 
00064     private $skin;
00065 
00069     private $config;
00070 
00074     private static $instance = null;
00075 
00081     public function setConfig( Config $c ) {
00082         $this->config = $c;
00083     }
00084 
00090     public function getConfig() {
00091         if ( $this->config === null ) {
00092             // @todo In the future, we could move this to WebStart.php so
00093             // the Config object is ready for when initialization happens
00094             $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
00095         }
00096 
00097         return $this->config;
00098     }
00099 
00105     public function setRequest( WebRequest $r ) {
00106         $this->request = $r;
00107     }
00108 
00114     public function getRequest() {
00115         if ( $this->request === null ) {
00116             global $wgRequest; # fallback to $wg till we can improve this
00117             $this->request = $wgRequest;
00118         }
00119 
00120         return $this->request;
00121     }
00122 
00128     public function setTitle( Title $title ) {
00129         $this->title = $title;
00130         // Erase the WikiPage so a new one with the new title gets created.
00131         $this->wikipage = null;
00132     }
00133 
00139     public function getTitle() {
00140         if ( $this->title === null ) {
00141             global $wgTitle; # fallback to $wg till we can improve this
00142             $this->title = $wgTitle;
00143         }
00144 
00145         return $this->title;
00146     }
00147 
00156     public function canUseWikiPage() {
00157         if ( $this->wikipage ) {
00158             // If there's a WikiPage object set, we can for sure get it
00159             return true;
00160         }
00161         // Only pages with legitimate titles can have WikiPages.
00162         // That usually means pages in non-virtual namespaces.
00163         $title = $this->getTitle();
00164         return $title ? $title->canExist() : false;
00165     }
00166 
00173     public function setWikiPage( WikiPage $p ) {
00174         $contextTitle = $this->getTitle();
00175         $pageTitle = $p->getTitle();
00176         if ( !$contextTitle || !$pageTitle->equals( $contextTitle ) ) {
00177             $this->setTitle( $pageTitle );
00178         }
00179         // Defer this to the end since setTitle sets it to null.
00180         $this->wikipage = $p;
00181     }
00182 
00193     public function getWikiPage() {
00194         if ( $this->wikipage === null ) {
00195             $title = $this->getTitle();
00196             if ( $title === null ) {
00197                 throw new MWException( __METHOD__ . ' called without Title object set' );
00198             }
00199             $this->wikipage = WikiPage::factory( $title );
00200         }
00201 
00202         return $this->wikipage;
00203     }
00204 
00208     public function setOutput( OutputPage $o ) {
00209         $this->output = $o;
00210     }
00211 
00217     public function getOutput() {
00218         if ( $this->output === null ) {
00219             $this->output = new OutputPage( $this );
00220         }
00221 
00222         return $this->output;
00223     }
00224 
00230     public function setUser( User $u ) {
00231         $this->user = $u;
00232     }
00233 
00239     public function getUser() {
00240         if ( $this->user === null ) {
00241             $this->user = User::newFromSession( $this->getRequest() );
00242         }
00243 
00244         return $this->user;
00245     }
00246 
00253     public static function sanitizeLangCode( $code ) {
00254         global $wgLanguageCode;
00255 
00256         // BCP 47 - letter case MUST NOT carry meaning
00257         $code = strtolower( $code );
00258 
00259         # Validate $code
00260         if ( !$code || !Language::isValidCode( $code ) || $code === 'qqq' ) {
00261             wfDebug( "Invalid user language code\n" );
00262             $code = $wgLanguageCode;
00263         }
00264 
00265         return $code;
00266     }
00267 
00275     public function setLanguage( $l ) {
00276         if ( $l instanceof Language ) {
00277             $this->lang = $l;
00278         } elseif ( is_string( $l ) ) {
00279             $l = self::sanitizeLangCode( $l );
00280             $obj = Language::factory( $l );
00281             $this->lang = $obj;
00282         } else {
00283             throw new MWException( __METHOD__ . " was passed an invalid type of data." );
00284         }
00285     }
00286 
00294     public function getLanguage() {
00295         if ( isset( $this->recursion ) ) {
00296             trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING );
00297             $e = new Exception;
00298             wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() );
00299 
00300             $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en';
00301             $this->lang = Language::factory( $code );
00302         } elseif ( $this->lang === null ) {
00303             $this->recursion = true;
00304 
00305             global $wgContLang;
00306 
00307             try {
00308                 $request = $this->getRequest();
00309                 $user = $this->getUser();
00310 
00311                 $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
00312                 $code = self::sanitizeLangCode( $code );
00313 
00314                 wfRunHooks( 'UserGetLanguageObject', array( $user, &$code, $this ) );
00315 
00316                 if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) {
00317                     $this->lang = $wgContLang;
00318                 } else {
00319                     $obj = Language::factory( $code );
00320                     $this->lang = $obj;
00321                 }
00322 
00323                 unset( $this->recursion );
00324             }
00325             catch ( Exception $ex ) {
00326                 unset( $this->recursion );
00327                 throw $ex;
00328             }
00329         }
00330 
00331         return $this->lang;
00332     }
00333 
00339     public function setSkin( Skin $s ) {
00340         $this->skin = clone $s;
00341         $this->skin->setContext( $this );
00342     }
00343 
00349     public function getSkin() {
00350         if ( $this->skin === null ) {
00351             wfProfileIn( __METHOD__ . '-createskin' );
00352 
00353             $skin = null;
00354             wfRunHooks( 'RequestContextCreateSkin', array( $this, &$skin ) );
00355             $factory = SkinFactory::getDefaultInstance();
00356 
00357             // If the hook worked try to set a skin from it
00358             if ( $skin instanceof Skin ) {
00359                 $this->skin = $skin;
00360             } elseif ( is_string( $skin ) ) {
00361                 // Normalize the key, just in case the hook did something weird.
00362                 $normalized = Skin::normalizeKey( $skin );
00363                 $this->skin = $factory->makeSkin( $normalized );
00364             }
00365 
00366             // If this is still null (the hook didn't run or didn't work)
00367             // then go through the normal processing to load a skin
00368             if ( $this->skin === null ) {
00369                 if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) {
00370                     # get the user skin
00371                     $userSkin = $this->getUser()->getOption( 'skin' );
00372                     $userSkin = $this->getRequest()->getVal( 'useskin', $userSkin );
00373                 } else {
00374                     # if we're not allowing users to override, then use the default
00375                     $userSkin = $this->getConfig()->get( 'DefaultSkin' );
00376                 }
00377 
00378                 // Normalize the key in case the user is passing gibberish
00379                 // or has old preferences (bug 69566).
00380                 $normalized = Skin::normalizeKey( $userSkin );
00381 
00382                 // Skin::normalizeKey will also validate it, so
00383                 // this won't throw an exception
00384                 $this->skin = $factory->makeSkin( $normalized );
00385             }
00386 
00387             // After all that set a context on whatever skin got created
00388             $this->skin->setContext( $this );
00389             wfProfileOut( __METHOD__ . '-createskin' );
00390         }
00391 
00392         return $this->skin;
00393     }
00394 
00403     public function msg() {
00404         $args = func_get_args();
00405 
00406         return call_user_func_array( 'wfMessage', $args )->setContext( $this );
00407     }
00408 
00416     public static function getMain() {
00417         if ( self::$instance === null ) {
00418             self::$instance = new self;
00419         }
00420 
00421         return self::$instance;
00422     }
00423 
00432     public static function getMainAndWarn( $func = __METHOD__ ) {
00433         wfDebug( $func . ' called without context. ' .
00434             "Using RequestContext::getMain() for sanity\n" );
00435 
00436         return self::getMain();
00437     }
00438 
00442     public static function resetMain() {
00443         if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
00444             throw new MWException( __METHOD__ . '() should be called only from unit tests!' );
00445         }
00446         self::$instance = null;
00447     }
00448 
00456     public function exportSession() {
00457         return array(
00458             'ip' => $this->getRequest()->getIP(),
00459             'headers' => $this->getRequest()->getAllHeaders(),
00460             'sessionId' => session_id(),
00461             'userId' => $this->getUser()->getId()
00462         );
00463     }
00464 
00481     public static function importScopedSession( array $params ) {
00482         if ( PHP_SAPI !== 'cli' ) {
00483             // Don't send random private cookies or turn $wgRequest into FauxRequest
00484             throw new MWException( "Sessions can only be imported in cli mode." );
00485         } elseif ( !strlen( $params['sessionId'] ) ) {
00486             throw new MWException( "No session ID was specified." );
00487         }
00488 
00489         if ( $params['userId'] ) { // logged-in user
00490             $user = User::newFromId( $params['userId'] );
00491             $user->load();
00492             if ( !$user->getId() ) {
00493                 throw new MWException( "No user with ID '{$params['userId']}'." );
00494             }
00495         } elseif ( !IP::isValid( $params['ip'] ) ) {
00496             throw new MWException( "Could not load user '{$params['ip']}'." );
00497         } else { // anon user
00498             $user = User::newFromName( $params['ip'], false );
00499         }
00500 
00501         $importSessionFunction = function ( User $user, array $params ) {
00502             global $wgRequest, $wgUser;
00503 
00504             $context = RequestContext::getMain();
00505             // Commit and close any current session
00506             session_write_close(); // persist
00507             session_id( '' ); // detach
00508             $_SESSION = array(); // clear in-memory array
00509             // Remove any user IP or agent information
00510             $context->setRequest( new FauxRequest() );
00511             $wgRequest = $context->getRequest(); // b/c
00512             // Now that all private information is detached from the user, it should
00513             // be safe to load the new user. If errors occur or an exception is thrown
00514             // and caught (leaving the main context in a mixed state), there is no risk
00515             // of the User object being attached to the wrong IP, headers, or session.
00516             $context->setUser( $user );
00517             $wgUser = $context->getUser(); // b/c
00518             if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID
00519                 wfSetupSession( $params['sessionId'] ); // sets $_SESSION
00520             }
00521             $request = new FauxRequest( array(), false, $_SESSION );
00522             $request->setIP( $params['ip'] );
00523             foreach ( $params['headers'] as $name => $value ) {
00524                 $request->setHeader( $name, $value );
00525             }
00526             // Set the current context to use the new WebRequest
00527             $context->setRequest( $request );
00528             $wgRequest = $context->getRequest(); // b/c
00529         };
00530 
00531         // Stash the old session and load in the new one
00532         $oUser = self::getMain()->getUser();
00533         $oParams = self::getMain()->exportSession();
00534         $importSessionFunction( $user, $params );
00535 
00536         // Set callback to save and close the new session and reload the old one
00537         return new ScopedCallback( function () use ( $importSessionFunction, $oUser, $oParams ) {
00538             $importSessionFunction( $oUser, $oParams );
00539         } );
00540     }
00541 
00556     public static function newExtraneousContext( Title $title, $request = array() ) {
00557         $context = new self;
00558         $context->setTitle( $title );
00559         if ( $request instanceof WebRequest ) {
00560             $context->setRequest( $request );
00561         } else {
00562             $context->setRequest( new FauxRequest( $request ) );
00563         }
00564         $context->user = User::newFromName( '127.0.0.1', false );
00565 
00566         return $context;
00567     }
00568 }