[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/context/ -> RequestContext.php (source)

   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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1