Merge "Monobook: Solve padding issues with #content and #firstheading"
[lhc/web/wiklou.git] / includes / context / RequestContext.php
index 09cb409..6aefc98 100644 (file)
@@ -392,6 +392,96 @@ class RequestContext implements IContextSource {
                return $instance;
        }
 
+       /**
+        * Export the resolved user IP, HTTP headers, user ID, and session ID.
+        * The result will be reasonably sized to allow for serialization.
+        *
+        * @return Array
+        * @since 1.21
+        */
+       public function exportSession() {
+               return array(
+                       'ip'        => $this->getRequest()->getIP(),
+                       'headers'   => $this->getRequest()->getAllHeaders(),
+                       'sessionId' => session_id(),
+                       'userId'    => $this->getUser()->getId()
+               );
+       }
+
+       /**
+        * Import the resolved user IP, HTTP headers, user ID, and session ID.
+        * This sets the current session and sets $wgUser and $wgRequest.
+        * Once the return value falls out of scope, the old context is restored.
+        * This function can only be called within CLI mode scripts.
+        *
+        * This will setup the session from the given ID. This is useful when
+        * background scripts inherit context when acting on behalf of a user.
+        *
+        * $param array $params Result of RequestContext::exportSession()
+        * @return ScopedCallback
+        * @throws MWException
+        * @since 1.21
+        */
+       public static function importScopedSession( array $params ) {
+               if ( PHP_SAPI !== 'cli' ) {
+                       // Don't send random private cookies or turn $wgRequest into FauxRequest
+                       throw new MWException( "Sessions can only be imported in cli mode." );
+               } elseif ( !strlen( $params['sessionId'] ) ) {
+                       throw new MWException( "No session ID was specified." );
+               }
+
+               if ( $params['userId'] ) { // logged-in user
+                       $user = User::newFromId( $params['userId'] );
+                       if ( !$user ) {
+                               throw new MWException( "No user with ID '{$params['userId']}'." );
+                       }
+               } elseif ( !IP::isValid( $params['ip'] ) ) {
+                       throw new MWException( "Could not load user '{$params['ip']}'." );
+               } else { // anon user
+                       $user = User::newFromName( $params['ip'], false );
+               }
+
+               $importSessionFunction = function( User $user, array $params ) {
+                       global $wgRequest, $wgUser;
+
+                       $context = RequestContext::getMain();
+                       // Commit and close any current session
+                       session_write_close(); // persist
+                       session_id( '' ); // detach
+                       $_SESSION = array(); // clear in-memory array
+                       // Remove any user IP or agent information
+                       $context->setRequest( new FauxRequest() );
+                       $wgRequest = $context->getRequest(); // b/c
+                       // Now that all private information is detached from the user, it should
+                       // be safe to load the new user. If errors occur or an exception is thrown
+                       // and caught (leaving the main context in a mixed state), there is no risk
+                       // of the User object being attached to the wrong IP, headers, or session.
+                       $context->setUser( $user );
+                       $wgUser = $context->getUser(); // b/c
+                       if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID
+                               wfSetupSession( $params['sessionId'] ); // sets $_SESSION
+                       }
+                       $request = new FauxRequest( array(), false, $_SESSION );
+                       $request->setIP( $params['ip'] );
+                       foreach ( $params['headers'] as $name => $value ) {
+                               $request->setHeader( $name, $value );
+                       }
+                       // Set the current context to use the new WebRequest
+                       $context->setRequest( $request );
+                       $wgRequest = $context->getRequest(); // b/c
+               };
+
+               // Stash the old session and load in the new one
+               $oUser = self::getMain()->getUser();
+               $oParams = self::getMain()->exportSession();
+               $importSessionFunction( $user, $params );
+
+               // Set callback to save and close the new session and reload the old one
+               return new ScopedCallback( function() use ( $importSessionFunction, $oUser, $oParams ) {
+                       $importSessionFunction( $oUser, $oParams );
+               } );
+       }
+
        /**
         * Create a new extraneous context. The context is filled with information
         * external to the current session.