[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> OutputPage.php (source)

   1  <?php
   2  /**
   3   * Preparation for the final page rendering.
   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   * @file
  21   */
  22  
  23  /**
  24   * This class should be covered by a general architecture document which does
  25   * not exist as of January 2011.  This is one of the Core classes and should
  26   * be read at least once by any new developers.
  27   *
  28   * This class is used to prepare the final rendering. A skin is then
  29   * applied to the output parameters (links, javascript, html, categories ...).
  30   *
  31   * @todo FIXME: Another class handles sending the whole page to the client.
  32   *
  33   * Some comments comes from a pairing session between Zak Greant and Antoine Musso
  34   * in November 2010.
  35   *
  36   * @todo document
  37   */
  38  class OutputPage extends ContextSource {
  39      /** @var array Should be private. Used with addMeta() which adds "<meta>" */
  40      protected $mMetatags = array();
  41  
  42      /** @var array */
  43      protected $mLinktags = array();
  44  
  45      /** @var bool */
  46      protected $mCanonicalUrl = false;
  47  
  48      /**
  49       * @var array Additional stylesheets. Looks like this is for extensions.
  50       *   Might be replaced by resource loader.
  51       */
  52      protected $mExtStyles = array();
  53  
  54      /**
  55       * @var string Should be private - has getter and setter. Contains
  56       *   the HTML title */
  57      public $mPagetitle = '';
  58  
  59      /**
  60       * @var string Contains all of the "<body>" content. Should be private we
  61       *   got set/get accessors and the append() method.
  62       */
  63      public $mBodytext = '';
  64  
  65      /**
  66       * Holds the debug lines that will be output as comments in page source if
  67       * $wgDebugComments is enabled. See also $wgShowDebug.
  68       * @deprecated since 1.20; use MWDebug class instead.
  69       */
  70      public $mDebugtext = '';
  71  
  72      /** @var string Stores contents of "<title>" tag */
  73      private $mHTMLtitle = '';
  74  
  75      /**
  76       * @var bool Is the displayed content related to the source of the
  77       *   corresponding wiki article.
  78       */
  79      private $mIsarticle = false;
  80  
  81      /** @var bool Stores "article flag" toggle. */
  82      private $mIsArticleRelated = true;
  83  
  84      /**
  85       * @var bool We have to set isPrintable(). Some pages should
  86       * never be printed (ex: redirections).
  87       */
  88      private $mPrintable = false;
  89  
  90      /**
  91       * @var array Contains the page subtitle. Special pages usually have some
  92       *   links here. Don't confuse with site subtitle added by skins.
  93       */
  94      private $mSubtitle = array();
  95  
  96      /** @var string */
  97      public $mRedirect = '';
  98  
  99      /** @var int */
 100      protected $mStatusCode;
 101  
 102      /**
 103       * @var string Variable mLastModified and mEtag are used for sending cache control.
 104       *   The whole caching system should probably be moved into its own class.
 105       */
 106      protected $mLastModified = '';
 107  
 108      /**
 109       * Contains an HTTP Entity Tags (see RFC 2616 section 3.13) which is used
 110       * as a unique identifier for the content. It is later used by the client
 111       * to compare its cached version with the server version. Client sends
 112       * headers If-Match and If-None-Match containing its locally cached ETAG value.
 113       *
 114       * To get more information, you will have to look at HTTP/1.1 protocol which
 115       * is properly described in RFC 2616 : http://tools.ietf.org/html/rfc2616
 116       */
 117      private $mETag = false;
 118  
 119      /** @var array */
 120      protected $mCategoryLinks = array();
 121  
 122      /** @var array */
 123      protected $mCategories = array();
 124  
 125      /** @var array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page') */
 126      private $mLanguageLinks = array();
 127  
 128      /**
 129       * Used for JavaScript (pre resource loader)
 130       * @todo We should split JS / CSS.
 131       * mScripts content is inserted as is in "<head>" by Skin. This might
 132       * contain either a link to a stylesheet or inline CSS.
 133       */
 134      private $mScripts = '';
 135  
 136      /** @var string Inline CSS styles. Use addInlineStyle() sparingly */
 137      protected $mInlineStyles = '';
 138  
 139      /** @todo Unused? */
 140      private $mLinkColours;
 141  
 142      /**
 143       * @var string Used by skin template.
 144       * Example: $tpl->set( 'displaytitle', $out->mPageLinkTitle );
 145       */
 146      public $mPageLinkTitle = '';
 147  
 148      /** @var array Array of elements in "<head>". Parser might add its own headers! */
 149      protected $mHeadItems = array();
 150  
 151      // @todo FIXME: Next 5 variables probably come from the resource loader
 152  
 153      /** @var array */
 154      protected $mModules = array();
 155  
 156      /** @var array */
 157      protected $mModuleScripts = array();
 158  
 159      /** @var array */
 160      protected $mModuleStyles = array();
 161  
 162      /** @var array */
 163      protected $mModuleMessages = array();
 164  
 165      /** @var ResourceLoader */
 166      protected $mResourceLoader;
 167  
 168      /** @var array */
 169      protected $mJsConfigVars = array();
 170  
 171      /** @var array */
 172      protected $mTemplateIds = array();
 173  
 174      /** @var array */
 175      protected $mImageTimeKeys = array();
 176  
 177      /** @var string */
 178      public $mRedirectCode = '';
 179  
 180      protected $mFeedLinksAppendQuery = null;
 181  
 182      /** @var array
 183       * What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
 184       * @see ResourceLoaderModule::$origin
 185       * ResourceLoaderModule::ORIGIN_ALL is assumed unless overridden;
 186       */
 187      protected $mAllowedModules = array(
 188          ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL,
 189      );
 190  
 191      /** @var bool Whether output is disabled.  If this is true, the 'output' method will do nothing. */
 192      protected $mDoNothing = false;
 193  
 194      // Parser related.
 195  
 196      /**
 197       * @var int
 198       * @todo Unused?
 199       */
 200      private $mContainsOldMagic = 0;
 201  
 202      /** @var int */
 203      protected $mContainsNewMagic = 0;
 204  
 205      /**
 206       * lazy initialised, use parserOptions()
 207       * @var ParserOptions
 208       */
 209      protected $mParserOptions = null;
 210  
 211      /**
 212       * Handles the Atom / RSS links.
 213       * We probably only support Atom in 2011.
 214       * @see $wgAdvertisedFeedTypes
 215       */
 216      private $mFeedLinks = array();
 217  
 218      // Gwicke work on squid caching? Roughly from 2003.
 219      protected $mEnableClientCache = true;
 220  
 221      /** @var bool Flag if output should only contain the body of the article. */
 222      private $mArticleBodyOnly = false;
 223  
 224      /** @var bool */
 225      protected $mNewSectionLink = false;
 226  
 227      /** @var bool */
 228      protected $mHideNewSectionLink = false;
 229  
 230      /**
 231       * @var bool Comes from the parser. This was probably made to load CSS/JS
 232       * only if we had "<gallery>". Used directly in CategoryPage.php.
 233       * Looks like resource loader can replace this.
 234       */
 235      public $mNoGallery = false;
 236  
 237      /** @var string */
 238      private $mPageTitleActionText = '';
 239  
 240      /** @var array */
 241      private $mParseWarnings = array();
 242  
 243      /** @var int Cache stuff. Looks like mEnableClientCache */
 244      protected $mSquidMaxage = 0;
 245  
 246      /**
 247       * @var bool
 248       * @todo Document
 249       */
 250      protected $mPreventClickjacking = true;
 251  
 252      /** @var int To include the variable {{REVISIONID}} */
 253      private $mRevisionId = null;
 254  
 255      /** @var string */
 256      private $mRevisionTimestamp = null;
 257  
 258      /** @var array */
 259      protected $mFileVersion = null;
 260  
 261      /**
 262       * @var array An array of stylesheet filenames (relative from skins path),
 263       * with options for CSS media, IE conditions, and RTL/LTR direction.
 264       * For internal use; add settings in the skin via $this->addStyle()
 265       *
 266       * Style again! This seems like a code duplication since we already have
 267       * mStyles. This is what makes Open Source amazing.
 268       */
 269      protected $styles = array();
 270  
 271      /**
 272       * Whether jQuery is already handled.
 273       */
 274      protected $mJQueryDone = false;
 275  
 276      private $mIndexPolicy = 'index';
 277      private $mFollowPolicy = 'follow';
 278      private $mVaryHeader = array(
 279          'Accept-Encoding' => array( 'list-contains=gzip' ),
 280      );
 281  
 282      /**
 283       * If the current page was reached through a redirect, $mRedirectedFrom contains the Title
 284       * of the redirect.
 285       *
 286       * @var Title
 287       */
 288      private $mRedirectedFrom = null;
 289  
 290      /**
 291       * Additional key => value data
 292       */
 293      private $mProperties = array();
 294  
 295      /**
 296       * @var string|null ResourceLoader target for load.php links. If null, will be omitted
 297       */
 298      private $mTarget = null;
 299  
 300      /**
 301       * @var bool Whether parser output should contain table of contents
 302       */
 303      private $mEnableTOC = true;
 304  
 305      /**
 306       * @var bool Whether parser output should contain section edit links
 307       */
 308      private $mEnableSectionEditLinks = true;
 309  
 310      /**
 311       * Constructor for OutputPage. This should not be called directly.
 312       * Instead a new RequestContext should be created and it will implicitly create
 313       * a OutputPage tied to that context.
 314       * @param IContextSource|null $context
 315       */
 316  	function __construct( IContextSource $context = null ) {
 317          if ( $context === null ) {
 318              # Extensions should use `new RequestContext` instead of `new OutputPage` now.
 319              wfDeprecated( __METHOD__, '1.18' );
 320          } else {
 321              $this->setContext( $context );
 322          }
 323      }
 324  
 325      /**
 326       * Redirect to $url rather than displaying the normal page
 327       *
 328       * @param string $url URL
 329       * @param string $responsecode HTTP status code
 330       */
 331  	public function redirect( $url, $responsecode = '302' ) {
 332          # Strip newlines as a paranoia check for header injection in PHP<5.1.2
 333          $this->mRedirect = str_replace( "\n", '', $url );
 334          $this->mRedirectCode = $responsecode;
 335      }
 336  
 337      /**
 338       * Get the URL to redirect to, or an empty string if not redirect URL set
 339       *
 340       * @return string
 341       */
 342  	public function getRedirect() {
 343          return $this->mRedirect;
 344      }
 345  
 346      /**
 347       * Set the HTTP status code to send with the output.
 348       *
 349       * @param int $statusCode
 350       */
 351  	public function setStatusCode( $statusCode ) {
 352          $this->mStatusCode = $statusCode;
 353      }
 354  
 355      /**
 356       * Add a new "<meta>" tag
 357       * To add an http-equiv meta tag, precede the name with "http:"
 358       *
 359       * @param string $name Tag name
 360       * @param string $val Tag value
 361       */
 362  	function addMeta( $name, $val ) {
 363          array_push( $this->mMetatags, array( $name, $val ) );
 364      }
 365  
 366      /**
 367       * Add a new \<link\> tag to the page header.
 368       *
 369       * Note: use setCanonicalUrl() for rel=canonical.
 370       *
 371       * @param array $linkarr Associative array of attributes.
 372       */
 373  	function addLink( array $linkarr ) {
 374          array_push( $this->mLinktags, $linkarr );
 375      }
 376  
 377      /**
 378       * Add a new \<link\> with "rel" attribute set to "meta"
 379       *
 380       * @param array $linkarr Associative array mapping attribute names to their
 381       *                 values, both keys and values will be escaped, and the
 382       *                 "rel" attribute will be automatically added
 383       */
 384  	function addMetadataLink( array $linkarr ) {
 385          $linkarr['rel'] = $this->getMetadataAttribute();
 386          $this->addLink( $linkarr );
 387      }
 388  
 389      /**
 390       * Set the URL to be used for the <link rel=canonical>. This should be used
 391       * in preference to addLink(), to avoid duplicate link tags.
 392       * @param string $url
 393       */
 394  	function setCanonicalUrl( $url ) {
 395          $this->mCanonicalUrl = $url;
 396      }
 397  
 398      /**
 399       * Get the value of the "rel" attribute for metadata links
 400       *
 401       * @return string
 402       */
 403  	public function getMetadataAttribute() {
 404          # note: buggy CC software only reads first "meta" link
 405          static $haveMeta = false;
 406          if ( $haveMeta ) {
 407              return 'alternate meta';
 408          } else {
 409              $haveMeta = true;
 410              return 'meta';
 411          }
 412      }
 413  
 414      /**
 415       * Add raw HTML to the list of scripts (including \<script\> tag, etc.)
 416       *
 417       * @param string $script Raw HTML
 418       */
 419  	function addScript( $script ) {
 420          $this->mScripts .= $script . "\n";
 421      }
 422  
 423      /**
 424       * Register and add a stylesheet from an extension directory.
 425       *
 426       * @param string $url Path to sheet.  Provide either a full url (beginning
 427       *             with 'http', etc) or a relative path from the document root
 428       *             (beginning with '/').  Otherwise it behaves identically to
 429       *             addStyle() and draws from the /skins folder.
 430       */
 431  	public function addExtensionStyle( $url ) {
 432          array_push( $this->mExtStyles, $url );
 433      }
 434  
 435      /**
 436       * Get all styles added by extensions
 437       *
 438       * @return array
 439       */
 440  	function getExtStyle() {
 441          return $this->mExtStyles;
 442      }
 443  
 444      /**
 445       * Add a JavaScript file out of skins/common, or a given relative path.
 446       *
 447       * @param string $file Filename in skins/common or complete on-server path
 448       *              (/foo/bar.js)
 449       * @param string $version Style version of the file. Defaults to $wgStyleVersion
 450       */
 451  	public function addScriptFile( $file, $version = null ) {
 452          // See if $file parameter is an absolute URL or begins with a slash
 453          if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
 454              $path = $file;
 455          } else {
 456              $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}";
 457          }
 458          if ( is_null( $version ) ) {
 459              $version = $this->getConfig()->get( 'StyleVersion' );
 460          }
 461          $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
 462      }
 463  
 464      /**
 465       * Add a self-contained script tag with the given contents
 466       *
 467       * @param string $script JavaScript text, no "<script>" tags
 468       */
 469  	public function addInlineScript( $script ) {
 470          $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
 471      }
 472  
 473      /**
 474       * Get all registered JS and CSS tags for the header.
 475       *
 476       * @return string
 477       * @deprecated since 1.24 Use OutputPage::headElement to build the full header.
 478       */
 479  	function getScript() {
 480          wfDeprecated( __METHOD__, '1.24' );
 481          return $this->mScripts . $this->getHeadItems();
 482      }
 483  
 484      /**
 485       * Filter an array of modules to remove insufficiently trustworthy members, and modules
 486       * which are no longer registered (eg a page is cached before an extension is disabled)
 487       * @param array $modules
 488       * @param string|null $position If not null, only return modules with this position
 489       * @param string $type
 490       * @return array
 491       */
 492  	protected function filterModules( array $modules, $position = null,
 493          $type = ResourceLoaderModule::TYPE_COMBINED
 494      ) {
 495          $resourceLoader = $this->getResourceLoader();
 496          $filteredModules = array();
 497          foreach ( $modules as $val ) {
 498              $module = $resourceLoader->getModule( $val );
 499              if ( $module instanceof ResourceLoaderModule
 500                  && $module->getOrigin() <= $this->getAllowedModules( $type )
 501                  && ( is_null( $position ) || $module->getPosition() == $position )
 502                  && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) )
 503              ) {
 504                  $filteredModules[] = $val;
 505              }
 506          }
 507          return $filteredModules;
 508      }
 509  
 510      /**
 511       * Get the list of modules to include on this page
 512       *
 513       * @param bool $filter Whether to filter out insufficiently trustworthy modules
 514       * @param string|null $position If not null, only return modules with this position
 515       * @param string $param
 516       * @return array Array of module names
 517       */
 518  	public function getModules( $filter = false, $position = null, $param = 'mModules' ) {
 519          $modules = array_values( array_unique( $this->$param ) );
 520          return $filter
 521              ? $this->filterModules( $modules, $position )
 522              : $modules;
 523      }
 524  
 525      /**
 526       * Add one or more modules recognized by the resource loader. Modules added
 527       * through this function will be loaded by the resource loader when the
 528       * page loads.
 529       *
 530       * @param string|array $modules Module name (string) or array of module names
 531       */
 532  	public function addModules( $modules ) {
 533          $this->mModules = array_merge( $this->mModules, (array)$modules );
 534      }
 535  
 536      /**
 537       * Get the list of module JS to include on this page
 538       *
 539       * @param bool $filter
 540       * @param string|null $position
 541       *
 542       * @return array Array of module names
 543       */
 544  	public function getModuleScripts( $filter = false, $position = null ) {
 545          return $this->getModules( $filter, $position, 'mModuleScripts' );
 546      }
 547  
 548      /**
 549       * Add only JS of one or more modules recognized by the resource loader. Module
 550       * scripts added through this function will be loaded by the resource loader when
 551       * the page loads.
 552       *
 553       * @param string|array $modules Module name (string) or array of module names
 554       */
 555  	public function addModuleScripts( $modules ) {
 556          $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
 557      }
 558  
 559      /**
 560       * Get the list of module CSS to include on this page
 561       *
 562       * @param bool $filter
 563       * @param string|null $position
 564       *
 565       * @return array Array of module names
 566       */
 567  	public function getModuleStyles( $filter = false, $position = null ) {
 568          return $this->getModules( $filter, $position, 'mModuleStyles' );
 569      }
 570  
 571      /**
 572       * Add only CSS of one or more modules recognized by the resource loader.
 573       *
 574       * Module styles added through this function will be added using standard link CSS
 575       * tags, rather than as a combined Javascript and CSS package. Thus, they will
 576       * load when JavaScript is disabled (unless CSS also happens to be disabled).
 577       *
 578       * @param string|array $modules Module name (string) or array of module names
 579       */
 580  	public function addModuleStyles( $modules ) {
 581          $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
 582      }
 583  
 584      /**
 585       * Get the list of module messages to include on this page
 586       *
 587       * @param bool $filter
 588       * @param string|null $position
 589       *
 590       * @return array Array of module names
 591       */
 592  	public function getModuleMessages( $filter = false, $position = null ) {
 593          return $this->getModules( $filter, $position, 'mModuleMessages' );
 594      }
 595  
 596      /**
 597       * Add only messages of one or more modules recognized by the resource loader.
 598       * Module messages added through this function will be loaded by the resource
 599       * loader when the page loads.
 600       *
 601       * @param string|array $modules Module name (string) or array of module names
 602       */
 603  	public function addModuleMessages( $modules ) {
 604          $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
 605      }
 606  
 607      /**
 608       * @return null|string ResourceLoader target
 609       */
 610  	public function getTarget() {
 611          return $this->mTarget;
 612      }
 613  
 614      /**
 615       * Sets ResourceLoader target for load.php links. If null, will be omitted
 616       *
 617       * @param string|null $target
 618       */
 619  	public function setTarget( $target ) {
 620          $this->mTarget = $target;
 621      }
 622  
 623      /**
 624       * Get an array of head items
 625       *
 626       * @return array
 627       */
 628  	function getHeadItemsArray() {
 629          return $this->mHeadItems;
 630      }
 631  
 632      /**
 633       * Get all header items in a string
 634       *
 635       * @return string
 636       * @deprecated since 1.24 Use OutputPage::headElement or
 637       *   if absolutely necessary use OutputPage::getHeadItemsArray
 638       */
 639  	function getHeadItems() {
 640          wfDeprecated( __METHOD__, '1.24' );
 641          $s = '';
 642          foreach ( $this->mHeadItems as $item ) {
 643              $s .= $item;
 644          }
 645          return $s;
 646      }
 647  
 648      /**
 649       * Add or replace an header item to the output
 650       *
 651       * @param string $name Item name
 652       * @param string $value Raw HTML
 653       */
 654  	public function addHeadItem( $name, $value ) {
 655          $this->mHeadItems[$name] = $value;
 656      }
 657  
 658      /**
 659       * Check if the header item $name is already set
 660       *
 661       * @param string $name Item name
 662       * @return bool
 663       */
 664  	public function hasHeadItem( $name ) {
 665          return isset( $this->mHeadItems[$name] );
 666      }
 667  
 668      /**
 669       * Set the value of the ETag HTTP header, only used if $wgUseETag is true
 670       *
 671       * @param string $tag Value of "ETag" header
 672       */
 673  	function setETag( $tag ) {
 674          $this->mETag = $tag;
 675      }
 676  
 677      /**
 678       * Set whether the output should only contain the body of the article,
 679       * without any skin, sidebar, etc.
 680       * Used e.g. when calling with "action=render".
 681       *
 682       * @param bool $only Whether to output only the body of the article
 683       */
 684  	public function setArticleBodyOnly( $only ) {
 685          $this->mArticleBodyOnly = $only;
 686      }
 687  
 688      /**
 689       * Return whether the output will contain only the body of the article
 690       *
 691       * @return bool
 692       */
 693  	public function getArticleBodyOnly() {
 694          return $this->mArticleBodyOnly;
 695      }
 696  
 697      /**
 698       * Set an additional output property
 699       * @since 1.21
 700       *
 701       * @param string $name
 702       * @param mixed $value
 703       */
 704  	public function setProperty( $name, $value ) {
 705          $this->mProperties[$name] = $value;
 706      }
 707  
 708      /**
 709       * Get an additional output property
 710       * @since 1.21
 711       *
 712       * @param string $name
 713       * @return mixed Property value or null if not found
 714       */
 715  	public function getProperty( $name ) {
 716          if ( isset( $this->mProperties[$name] ) ) {
 717              return $this->mProperties[$name];
 718          } else {
 719              return null;
 720          }
 721      }
 722  
 723      /**
 724       * checkLastModified tells the client to use the client-cached page if
 725       * possible. If successful, the OutputPage is disabled so that
 726       * any future call to OutputPage->output() have no effect.
 727       *
 728       * Side effect: sets mLastModified for Last-Modified header
 729       *
 730       * @param string $timestamp
 731       *
 732       * @return bool True if cache-ok headers was sent.
 733       */
 734  	public function checkLastModified( $timestamp ) {
 735          if ( !$timestamp || $timestamp == '19700101000000' ) {
 736              wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
 737              return false;
 738          }
 739          $config = $this->getConfig();
 740          if ( !$config->get( 'CachePages' ) ) {
 741              wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
 742              return false;
 743          }
 744  
 745          $timestamp = wfTimestamp( TS_MW, $timestamp );
 746          $modifiedTimes = array(
 747              'page' => $timestamp,
 748              'user' => $this->getUser()->getTouched(),
 749              'epoch' => $config->get( 'CacheEpoch' )
 750          );
 751          if ( $config->get( 'UseSquid' ) ) {
 752              // bug 44570: the core page itself may not change, but resources might
 753              $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
 754          }
 755          wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
 756  
 757          $maxModified = max( $modifiedTimes );
 758          $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
 759  
 760          $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
 761          if ( $clientHeader === false ) {
 762              wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", 'log' );
 763              return false;
 764          }
 765  
 766          # IE sends sizes after the date like this:
 767          # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
 768          # this breaks strtotime().
 769          $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
 770  
 771          wfSuppressWarnings(); // E_STRICT system time bitching
 772          $clientHeaderTime = strtotime( $clientHeader );
 773          wfRestoreWarnings();
 774          if ( !$clientHeaderTime ) {
 775              wfDebug( __METHOD__
 776                  . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
 777              return false;
 778          }
 779          $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
 780  
 781          # Make debug info
 782          $info = '';
 783          foreach ( $modifiedTimes as $name => $value ) {
 784              if ( $info !== '' ) {
 785                  $info .= ', ';
 786              }
 787              $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
 788          }
 789  
 790          wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
 791              wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", 'log' );
 792          wfDebug( __METHOD__ . ": effective Last-Modified: " .
 793              wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", 'log' );
 794          if ( $clientHeaderTime < $maxModified ) {
 795              wfDebug( __METHOD__ . ": STALE, $info\n", 'log' );
 796              return false;
 797          }
 798  
 799          # Not modified
 800          # Give a 304 response code and disable body output
 801          wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", 'log' );
 802          ini_set( 'zlib.output_compression', 0 );
 803          $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" );
 804          $this->sendCacheControl();
 805          $this->disable();
 806  
 807          // Don't output a compressed blob when using ob_gzhandler;
 808          // it's technically against HTTP spec and seems to confuse
 809          // Firefox when the response gets split over two packets.
 810          wfClearOutputBuffers();
 811  
 812          return true;
 813      }
 814  
 815      /**
 816       * Override the last modified timestamp
 817       *
 818       * @param string $timestamp New timestamp, in a format readable by
 819       *        wfTimestamp()
 820       */
 821  	public function setLastModified( $timestamp ) {
 822          $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
 823      }
 824  
 825      /**
 826       * Set the robot policy for the page: <http://www.robotstxt.org/meta.html>
 827       *
 828       * @param string $policy The literal string to output as the contents of
 829       *   the meta tag.  Will be parsed according to the spec and output in
 830       *   standardized form.
 831       * @return null
 832       */
 833  	public function setRobotPolicy( $policy ) {
 834          $policy = Article::formatRobotPolicy( $policy );
 835  
 836          if ( isset( $policy['index'] ) ) {
 837              $this->setIndexPolicy( $policy['index'] );
 838          }
 839          if ( isset( $policy['follow'] ) ) {
 840              $this->setFollowPolicy( $policy['follow'] );
 841          }
 842      }
 843  
 844      /**
 845       * Set the index policy for the page, but leave the follow policy un-
 846       * touched.
 847       *
 848       * @param string $policy Either 'index' or 'noindex'.
 849       * @return null
 850       */
 851  	public function setIndexPolicy( $policy ) {
 852          $policy = trim( $policy );
 853          if ( in_array( $policy, array( 'index', 'noindex' ) ) ) {
 854              $this->mIndexPolicy = $policy;
 855          }
 856      }
 857  
 858      /**
 859       * Set the follow policy for the page, but leave the index policy un-
 860       * touched.
 861       *
 862       * @param string $policy Either 'follow' or 'nofollow'.
 863       * @return null
 864       */
 865  	public function setFollowPolicy( $policy ) {
 866          $policy = trim( $policy );
 867          if ( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
 868              $this->mFollowPolicy = $policy;
 869          }
 870      }
 871  
 872      /**
 873       * Set the new value of the "action text", this will be added to the
 874       * "HTML title", separated from it with " - ".
 875       *
 876       * @param string $text New value of the "action text"
 877       */
 878  	public function setPageTitleActionText( $text ) {
 879          $this->mPageTitleActionText = $text;
 880      }
 881  
 882      /**
 883       * Get the value of the "action text"
 884       *
 885       * @return string
 886       */
 887  	public function getPageTitleActionText() {
 888          return $this->mPageTitleActionText;
 889      }
 890  
 891      /**
 892       * "HTML title" means the contents of "<title>".
 893       * It is stored as plain, unescaped text and will be run through htmlspecialchars in the skin file.
 894       *
 895       * @param string|Message $name
 896       */
 897  	public function setHTMLTitle( $name ) {
 898          if ( $name instanceof Message ) {
 899              $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
 900          } else {
 901              $this->mHTMLtitle = $name;
 902          }
 903      }
 904  
 905      /**
 906       * Return the "HTML title", i.e. the content of the "<title>" tag.
 907       *
 908       * @return string
 909       */
 910  	public function getHTMLTitle() {
 911          return $this->mHTMLtitle;
 912      }
 913  
 914      /**
 915       * Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
 916       *
 917       * @param Title $t
 918       */
 919  	public function setRedirectedFrom( $t ) {
 920          $this->mRedirectedFrom = $t;
 921      }
 922  
 923      /**
 924       * "Page title" means the contents of \<h1\>. It is stored as a valid HTML
 925       * fragment. This function allows good tags like \<sup\> in the \<h1\> tag,
 926       * but not bad tags like \<script\>. This function automatically sets
 927       * \<title\> to the same content as \<h1\> but with all tags removed. Bad
 928       * tags that were escaped in \<h1\> will still be escaped in \<title\>, and
 929       * good tags like \<i\> will be dropped entirely.
 930       *
 931       * @param string|Message $name
 932       */
 933  	public function setPageTitle( $name ) {
 934          if ( $name instanceof Message ) {
 935              $name = $name->setContext( $this->getContext() )->text();
 936          }
 937  
 938          # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
 939          # but leave "<i>foobar</i>" alone
 940          $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
 941          $this->mPagetitle = $nameWithTags;
 942  
 943          # change "<i>foo&amp;bar</i>" to "foo&bar"
 944          $this->setHTMLTitle(
 945              $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) )
 946                  ->inContentLanguage()
 947          );
 948      }
 949  
 950      /**
 951       * Return the "page title", i.e. the content of the \<h1\> tag.
 952       *
 953       * @return string
 954       */
 955  	public function getPageTitle() {
 956          return $this->mPagetitle;
 957      }
 958  
 959      /**
 960       * Set the Title object to use
 961       *
 962       * @param Title $t
 963       */
 964  	public function setTitle( Title $t ) {
 965          $this->getContext()->setTitle( $t );
 966      }
 967  
 968      /**
 969       * Replace the subtitle with $str
 970       *
 971       * @param string|Message $str New value of the subtitle. String should be safe HTML.
 972       */
 973  	public function setSubtitle( $str ) {
 974          $this->clearSubtitle();
 975          $this->addSubtitle( $str );
 976      }
 977  
 978      /**
 979       * Add $str to the subtitle
 980       *
 981       * @deprecated since 1.19; use addSubtitle() instead
 982       * @param string|Message $str String or Message to add to the subtitle
 983       */
 984  	public function appendSubtitle( $str ) {
 985          $this->addSubtitle( $str );
 986      }
 987  
 988      /**
 989       * Add $str to the subtitle
 990       *
 991       * @param string|Message $str String or Message to add to the subtitle. String should be safe HTML.
 992       */
 993  	public function addSubtitle( $str ) {
 994          if ( $str instanceof Message ) {
 995              $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
 996          } else {
 997              $this->mSubtitle[] = $str;
 998          }
 999      }
1000  
1001      /**
1002       * Add a subtitle containing a backlink to a page
1003       *
1004       * @param Title $title Title to link to
1005       * @param array $query Array of additional parameters to include in the link
1006       */
1007  	public function addBacklinkSubtitle( Title $title, $query = array() ) {
1008          if ( $title->isRedirect() ) {
1009              $query['redirect'] = 'no';
1010          }
1011          $this->addSubtitle( $this->msg( 'backlinksubtitle' )
1012              ->rawParams( Linker::link( $title, null, array(), $query ) ) );
1013      }
1014  
1015      /**
1016       * Clear the subtitles
1017       */
1018  	public function clearSubtitle() {
1019          $this->mSubtitle = array();
1020      }
1021  
1022      /**
1023       * Get the subtitle
1024       *
1025       * @return string
1026       */
1027  	public function getSubtitle() {
1028          return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1029      }
1030  
1031      /**
1032       * Set the page as printable, i.e. it'll be displayed with with all
1033       * print styles included
1034       */
1035  	public function setPrintable() {
1036          $this->mPrintable = true;
1037      }
1038  
1039      /**
1040       * Return whether the page is "printable"
1041       *
1042       * @return bool
1043       */
1044  	public function isPrintable() {
1045          return $this->mPrintable;
1046      }
1047  
1048      /**
1049       * Disable output completely, i.e. calling output() will have no effect
1050       */
1051  	public function disable() {
1052          $this->mDoNothing = true;
1053      }
1054  
1055      /**
1056       * Return whether the output will be completely disabled
1057       *
1058       * @return bool
1059       */
1060  	public function isDisabled() {
1061          return $this->mDoNothing;
1062      }
1063  
1064      /**
1065       * Show an "add new section" link?
1066       *
1067       * @return bool
1068       */
1069  	public function showNewSectionLink() {
1070          return $this->mNewSectionLink;
1071      }
1072  
1073      /**
1074       * Forcibly hide the new section link?
1075       *
1076       * @return bool
1077       */
1078  	public function forceHideNewSectionLink() {
1079          return $this->mHideNewSectionLink;
1080      }
1081  
1082      /**
1083       * Add or remove feed links in the page header
1084       * This is mainly kept for backward compatibility, see OutputPage::addFeedLink()
1085       * for the new version
1086       * @see addFeedLink()
1087       *
1088       * @param bool $show True: add default feeds, false: remove all feeds
1089       */
1090  	public function setSyndicated( $show = true ) {
1091          if ( $show ) {
1092              $this->setFeedAppendQuery( false );
1093          } else {
1094              $this->mFeedLinks = array();
1095          }
1096      }
1097  
1098      /**
1099       * Add default feeds to the page header
1100       * This is mainly kept for backward compatibility, see OutputPage::addFeedLink()
1101       * for the new version
1102       * @see addFeedLink()
1103       *
1104       * @param string $val Query to append to feed links or false to output
1105       *        default links
1106       */
1107  	public function setFeedAppendQuery( $val ) {
1108          $this->mFeedLinks = array();
1109  
1110          foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) {
1111              $query = "feed=$type";
1112              if ( is_string( $val ) ) {
1113                  $query .= '&' . $val;
1114              }
1115              $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1116          }
1117      }
1118  
1119      /**
1120       * Add a feed link to the page header
1121       *
1122       * @param string $format Feed type, should be a key of $wgFeedClasses
1123       * @param string $href URL
1124       */
1125  	public function addFeedLink( $format, $href ) {
1126          if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) {
1127              $this->mFeedLinks[$format] = $href;
1128          }
1129      }
1130  
1131      /**
1132       * Should we output feed links for this page?
1133       * @return bool
1134       */
1135  	public function isSyndicated() {
1136          return count( $this->mFeedLinks ) > 0;
1137      }
1138  
1139      /**
1140       * Return URLs for each supported syndication format for this page.
1141       * @return array Associating format keys with URLs
1142       */
1143  	public function getSyndicationLinks() {
1144          return $this->mFeedLinks;
1145      }
1146  
1147      /**
1148       * Will currently always return null
1149       *
1150       * @return null
1151       */
1152  	public function getFeedAppendQuery() {
1153          return $this->mFeedLinksAppendQuery;
1154      }
1155  
1156      /**
1157       * Set whether the displayed content is related to the source of the
1158       * corresponding article on the wiki
1159       * Setting true will cause the change "article related" toggle to true
1160       *
1161       * @param bool $v
1162       */
1163  	public function setArticleFlag( $v ) {
1164          $this->mIsarticle = $v;
1165          if ( $v ) {
1166              $this->mIsArticleRelated = $v;
1167          }
1168      }
1169  
1170      /**
1171       * Return whether the content displayed page is related to the source of
1172       * the corresponding article on the wiki
1173       *
1174       * @return bool
1175       */
1176  	public function isArticle() {
1177          return $this->mIsarticle;
1178      }
1179  
1180      /**
1181       * Set whether this page is related an article on the wiki
1182       * Setting false will cause the change of "article flag" toggle to false
1183       *
1184       * @param bool $v
1185       */
1186  	public function setArticleRelated( $v ) {
1187          $this->mIsArticleRelated = $v;
1188          if ( !$v ) {
1189              $this->mIsarticle = false;
1190          }
1191      }
1192  
1193      /**
1194       * Return whether this page is related an article on the wiki
1195       *
1196       * @return bool
1197       */
1198  	public function isArticleRelated() {
1199          return $this->mIsArticleRelated;
1200      }
1201  
1202      /**
1203       * Add new language links
1204       *
1205       * @param array $newLinkArray Associative array mapping language code to the page
1206       *                      name
1207       */
1208  	public function addLanguageLinks( array $newLinkArray ) {
1209          $this->mLanguageLinks += $newLinkArray;
1210      }
1211  
1212      /**
1213       * Reset the language links and add new language links
1214       *
1215       * @param array $newLinkArray Associative array mapping language code to the page
1216       *                      name
1217       */
1218  	public function setLanguageLinks( array $newLinkArray ) {
1219          $this->mLanguageLinks = $newLinkArray;
1220      }
1221  
1222      /**
1223       * Get the list of language links
1224       *
1225       * @return array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page')
1226       */
1227  	public function getLanguageLinks() {
1228          return $this->mLanguageLinks;
1229      }
1230  
1231      /**
1232       * Add an array of categories, with names in the keys
1233       *
1234       * @param array $categories Mapping category name => sort key
1235       */
1236  	public function addCategoryLinks( array $categories ) {
1237          global $wgContLang;
1238  
1239          if ( !is_array( $categories ) || count( $categories ) == 0 ) {
1240              return;
1241          }
1242  
1243          # Add the links to a LinkBatch
1244          $arr = array( NS_CATEGORY => $categories );
1245          $lb = new LinkBatch;
1246          $lb->setArray( $arr );
1247  
1248          # Fetch existence plus the hiddencat property
1249          $dbr = wfGetDB( DB_SLAVE );
1250          $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_len',
1251              'page_is_redirect', 'page_latest', 'pp_value' );
1252  
1253          if ( $this->getConfig()->get( 'ContentHandlerUseDB' ) ) {
1254              $fields[] = 'page_content_model';
1255          }
1256  
1257          $res = $dbr->select( array( 'page', 'page_props' ),
1258              $fields,
1259              $lb->constructSet( 'page', $dbr ),
1260              __METHOD__,
1261              array(),
1262              array( 'page_props' => array( 'LEFT JOIN', array(
1263                  'pp_propname' => 'hiddencat',
1264                  'pp_page = page_id'
1265              ) ) )
1266          );
1267  
1268          # Add the results to the link cache
1269          $lb->addResultToCache( LinkCache::singleton(), $res );
1270  
1271          # Set all the values to 'normal'.
1272          $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1273  
1274          # Mark hidden categories
1275          foreach ( $res as $row ) {
1276              if ( isset( $row->pp_value ) ) {
1277                  $categories[$row->page_title] = 'hidden';
1278              }
1279          }
1280  
1281          # Add the remaining categories to the skin
1282          if ( wfRunHooks(
1283              'OutputPageMakeCategoryLinks',
1284              array( &$this, $categories, &$this->mCategoryLinks ) )
1285          ) {
1286              foreach ( $categories as $category => $type ) {
1287                  $origcategory = $category;
1288                  $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1289                  if ( !$title ) {
1290                      continue;
1291                  }
1292                  $wgContLang->findVariantLink( $category, $title, true );
1293                  if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1294                      continue;
1295                  }
1296                  $text = $wgContLang->convertHtml( $title->getText() );
1297                  $this->mCategories[] = $title->getText();
1298                  $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
1299              }
1300          }
1301      }
1302  
1303      /**
1304       * Reset the category links (but not the category list) and add $categories
1305       *
1306       * @param array $categories Mapping category name => sort key
1307       */
1308  	public function setCategoryLinks( array $categories ) {
1309          $this->mCategoryLinks = array();
1310          $this->addCategoryLinks( $categories );
1311      }
1312  
1313      /**
1314       * Get the list of category links, in a 2-D array with the following format:
1315       * $arr[$type][] = $link, where $type is either "normal" or "hidden" (for
1316       * hidden categories) and $link a HTML fragment with a link to the category
1317       * page
1318       *
1319       * @return array
1320       */
1321  	public function getCategoryLinks() {
1322          return $this->mCategoryLinks;
1323      }
1324  
1325      /**
1326       * Get the list of category names this page belongs to
1327       *
1328       * @return array Array of strings
1329       */
1330  	public function getCategories() {
1331          return $this->mCategories;
1332      }
1333  
1334      /**
1335       * Do not allow scripts which can be modified by wiki users to load on this page;
1336       * only allow scripts bundled with, or generated by, the software.
1337       * Site-wide styles are controlled by a config setting, since they can be
1338       * used to create a custom skin/theme, but not user-specific ones.
1339       *
1340       * @todo this should be given a more accurate name
1341       */
1342  	public function disallowUserJs() {
1343          $this->reduceAllowedModules(
1344              ResourceLoaderModule::TYPE_SCRIPTS,
1345              ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
1346          );
1347  
1348          // Site-wide styles are controlled by a config setting, see bug 71621
1349          // for background on why. User styles are never allowed.
1350          if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1351              $styleOrigin = ResourceLoaderModule::ORIGIN_USER_SITEWIDE;
1352          } else {
1353              $styleOrigin = ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL;
1354          }
1355          $this->reduceAllowedModules(
1356              ResourceLoaderModule::TYPE_STYLES,
1357              $styleOrigin
1358          );
1359      }
1360  
1361      /**
1362       * Show what level of JavaScript / CSS untrustworthiness is allowed on this page
1363       * @see ResourceLoaderModule::$origin
1364       * @param string $type ResourceLoaderModule TYPE_ constant
1365       * @return int ResourceLoaderModule ORIGIN_ class constant
1366       */
1367  	public function getAllowedModules( $type ) {
1368          if ( $type == ResourceLoaderModule::TYPE_COMBINED ) {
1369              return min( array_values( $this->mAllowedModules ) );
1370          } else {
1371              return isset( $this->mAllowedModules[$type] )
1372                  ? $this->mAllowedModules[$type]
1373                  : ResourceLoaderModule::ORIGIN_ALL;
1374          }
1375      }
1376  
1377      /**
1378       * Set the highest level of CSS/JS untrustworthiness allowed
1379       *
1380       * @deprecated since 1.24 Raising level of allowed untrusted content is no longer supported.
1381       *  Use reduceAllowedModules() instead
1382       * @param string $type ResourceLoaderModule TYPE_ constant
1383       * @param int $level ResourceLoaderModule class constant
1384       */
1385  	public function setAllowedModules( $type, $level ) {
1386          wfDeprecated( __METHOD__, '1.24' );
1387          $this->reduceAllowedModules( $type, $level );
1388      }
1389  
1390      /**
1391       * Limit the highest level of CSS/JS untrustworthiness allowed.
1392       *
1393       * If passed the same or a higher level than the current level of untrustworthiness set, the
1394       * level will remain unchanged.
1395       *
1396       * @param string $type
1397       * @param int $level ResourceLoaderModule class constant
1398       */
1399  	public function reduceAllowedModules( $type, $level ) {
1400          $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1401      }
1402  
1403      /**
1404       * Prepend $text to the body HTML
1405       *
1406       * @param string $text HTML
1407       */
1408  	public function prependHTML( $text ) {
1409          $this->mBodytext = $text . $this->mBodytext;
1410      }
1411  
1412      /**
1413       * Append $text to the body HTML
1414       *
1415       * @param string $text HTML
1416       */
1417  	public function addHTML( $text ) {
1418          $this->mBodytext .= $text;
1419      }
1420  
1421      /**
1422       * Shortcut for adding an Html::element via addHTML.
1423       *
1424       * @since 1.19
1425       *
1426       * @param string $element
1427       * @param array $attribs
1428       * @param string $contents
1429       */
1430  	public function addElement( $element, array $attribs = array(), $contents = '' ) {
1431          $this->addHTML( Html::element( $element, $attribs, $contents ) );
1432      }
1433  
1434      /**
1435       * Clear the body HTML
1436       */
1437  	public function clearHTML() {
1438          $this->mBodytext = '';
1439      }
1440  
1441      /**
1442       * Get the body HTML
1443       *
1444       * @return string HTML
1445       */
1446  	public function getHTML() {
1447          return $this->mBodytext;
1448      }
1449  
1450      /**
1451       * Get/set the ParserOptions object to use for wikitext parsing
1452       *
1453       * @param ParserOptions|null $options Either the ParserOption to use or null to only get the
1454       *   current ParserOption object
1455       * @return ParserOptions
1456       */
1457  	public function parserOptions( $options = null ) {
1458          if ( !$this->mParserOptions ) {
1459              $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1460              $this->mParserOptions->setEditSection( false );
1461          }
1462          return wfSetVar( $this->mParserOptions, $options );
1463      }
1464  
1465      /**
1466       * Set the revision ID which will be seen by the wiki text parser
1467       * for things such as embedded {{REVISIONID}} variable use.
1468       *
1469       * @param int|null $revid An positive integer, or null
1470       * @return mixed Previous value
1471       */
1472  	public function setRevisionId( $revid ) {
1473          $val = is_null( $revid ) ? null : intval( $revid );
1474          return wfSetVar( $this->mRevisionId, $val );
1475      }
1476  
1477      /**
1478       * Get the displayed revision ID
1479       *
1480       * @return int
1481       */
1482  	public function getRevisionId() {
1483          return $this->mRevisionId;
1484      }
1485  
1486      /**
1487       * Set the timestamp of the revision which will be displayed. This is used
1488       * to avoid a extra DB call in Skin::lastModified().
1489       *
1490       * @param string|null $timestamp
1491       * @return mixed Previous value
1492       */
1493  	public function setRevisionTimestamp( $timestamp ) {
1494          return wfSetVar( $this->mRevisionTimestamp, $timestamp );
1495      }
1496  
1497      /**
1498       * Get the timestamp of displayed revision.
1499       * This will be null if not filled by setRevisionTimestamp().
1500       *
1501       * @return string|null
1502       */
1503  	public function getRevisionTimestamp() {
1504          return $this->mRevisionTimestamp;
1505      }
1506  
1507      /**
1508       * Set the displayed file version
1509       *
1510       * @param File|bool $file
1511       * @return mixed Previous value
1512       */
1513  	public function setFileVersion( $file ) {
1514          $val = null;
1515          if ( $file instanceof File && $file->exists() ) {
1516              $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() );
1517          }
1518          return wfSetVar( $this->mFileVersion, $val, true );
1519      }
1520  
1521      /**
1522       * Get the displayed file version
1523       *
1524       * @return array|null ('time' => MW timestamp, 'sha1' => sha1)
1525       */
1526  	public function getFileVersion() {
1527          return $this->mFileVersion;
1528      }
1529  
1530      /**
1531       * Get the templates used on this page
1532       *
1533       * @return array (namespace => dbKey => revId)
1534       * @since 1.18
1535       */
1536  	public function getTemplateIds() {
1537          return $this->mTemplateIds;
1538      }
1539  
1540      /**
1541       * Get the files used on this page
1542       *
1543       * @return array (dbKey => array('time' => MW timestamp or null, 'sha1' => sha1 or ''))
1544       * @since 1.18
1545       */
1546  	public function getFileSearchOptions() {
1547          return $this->mImageTimeKeys;
1548      }
1549  
1550      /**
1551       * Convert wikitext to HTML and add it to the buffer
1552       * Default assumes that the current page title will be used.
1553       *
1554       * @param string $text
1555       * @param bool $linestart Is this the start of a line?
1556       * @param bool $interface Is this text in the user interface language?
1557       */
1558  	public function addWikiText( $text, $linestart = true, $interface = true ) {
1559          $title = $this->getTitle(); // Work around E_STRICT
1560          if ( !$title ) {
1561              throw new MWException( 'Title is null' );
1562          }
1563          $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
1564      }
1565  
1566      /**
1567       * Add wikitext with a custom Title object
1568       *
1569       * @param string $text Wikitext
1570       * @param Title $title
1571       * @param bool $linestart Is this the start of a line?
1572       */
1573  	public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
1574          $this->addWikiTextTitle( $text, $title, $linestart );
1575      }
1576  
1577      /**
1578       * Add wikitext with a custom Title object and tidy enabled.
1579       *
1580       * @param string $text Wikitext
1581       * @param Title $title
1582       * @param bool $linestart Is this the start of a line?
1583       */
1584  	function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
1585          $this->addWikiTextTitle( $text, $title, $linestart, true );
1586      }
1587  
1588      /**
1589       * Add wikitext with tidy enabled
1590       *
1591       * @param string $text Wikitext
1592       * @param bool $linestart Is this the start of a line?
1593       */
1594  	public function addWikiTextTidy( $text, $linestart = true ) {
1595          $title = $this->getTitle();
1596          $this->addWikiTextTitleTidy( $text, $title, $linestart );
1597      }
1598  
1599      /**
1600       * Add wikitext with a custom Title object
1601       *
1602       * @param string $text Wikitext
1603       * @param Title $title
1604       * @param bool $linestart Is this the start of a line?
1605       * @param bool $tidy Whether to use tidy
1606       * @param bool $interface Whether it is an interface message
1607       *   (for example disables conversion)
1608       */
1609  	public function addWikiTextTitle( $text, Title $title, $linestart,
1610          $tidy = false, $interface = false
1611      ) {
1612          global $wgParser;
1613  
1614          wfProfileIn( __METHOD__ );
1615  
1616          $popts = $this->parserOptions();
1617          $oldTidy = $popts->setTidy( $tidy );
1618          $popts->setInterfaceMessage( (bool)$interface );
1619  
1620          $parserOutput = $wgParser->getFreshParser()->parse(
1621              $text, $title, $popts,
1622              $linestart, true, $this->mRevisionId
1623          );
1624  
1625          $popts->setTidy( $oldTidy );
1626  
1627          $this->addParserOutput( $parserOutput );
1628  
1629          wfProfileOut( __METHOD__ );
1630      }
1631  
1632      /**
1633       * Add a ParserOutput object, but without Html.
1634       *
1635       * @deprecated since 1.24, use addParserOutputMetadata() instead.
1636       * @param ParserOutput $parserOutput
1637       */
1638  	public function addParserOutputNoText( $parserOutput ) {
1639          $this->addParserOutputMetadata( $parserOutput );
1640      }
1641  
1642      /**
1643       * Add all metadata associated with a ParserOutput object, but without the actual HTML. This
1644       * includes categories, language links, ResourceLoader modules, effects of certain magic words,
1645       * and so on.
1646       *
1647       * @since 1.24
1648       * @param ParserOutput $parserOutput
1649       */
1650  	public function addParserOutputMetadata( $parserOutput ) {
1651          $this->mLanguageLinks += $parserOutput->getLanguageLinks();
1652          $this->addCategoryLinks( $parserOutput->getCategories() );
1653          $this->mNewSectionLink = $parserOutput->getNewSection();
1654          $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1655  
1656          $this->mParseWarnings = $parserOutput->getWarnings();
1657          if ( !$parserOutput->isCacheable() ) {
1658              $this->enableClientCache( false );
1659          }
1660          $this->mNoGallery = $parserOutput->getNoGallery();
1661          $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1662          $this->addModules( $parserOutput->getModules() );
1663          $this->addModuleScripts( $parserOutput->getModuleScripts() );
1664          $this->addModuleStyles( $parserOutput->getModuleStyles() );
1665          $this->addModuleMessages( $parserOutput->getModuleMessages() );
1666          $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1667          $this->mPreventClickjacking = $this->mPreventClickjacking
1668              || $parserOutput->preventClickjacking();
1669  
1670          // Template versioning...
1671          foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1672              if ( isset( $this->mTemplateIds[$ns] ) ) {
1673                  $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1674              } else {
1675                  $this->mTemplateIds[$ns] = $dbks;
1676              }
1677          }
1678          // File versioning...
1679          foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1680              $this->mImageTimeKeys[$dbk] = $data;
1681          }
1682  
1683          // Hooks registered in the object
1684          $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1685          foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1686              list( $hookName, $data ) = $hookInfo;
1687              if ( isset( $parserOutputHooks[$hookName] ) ) {
1688                  call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data );
1689              }
1690          }
1691  
1692          // Link flags are ignored for now, but may in the future be
1693          // used to mark individual language links.
1694          $linkFlags = array();
1695          wfRunHooks( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) );
1696          wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
1697      }
1698  
1699      /**
1700       * Add the HTML and enhancements for it (like ResourceLoader modules) associated with a
1701       * ParserOutput object, without any other metadata.
1702       *
1703       * @since 1.24
1704       * @param ParserOutput $parserOutput
1705       */
1706  	public function addParserOutputContent( $parserOutput ) {
1707          $this->addParserOutputText( $parserOutput );
1708  
1709          $this->addModules( $parserOutput->getModules() );
1710          $this->addModuleScripts( $parserOutput->getModuleScripts() );
1711          $this->addModuleStyles( $parserOutput->getModuleStyles() );
1712          $this->addModuleMessages( $parserOutput->getModuleMessages() );
1713  
1714          $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1715      }
1716  
1717      /**
1718       * Add the HTML associated with a ParserOutput object, without any metadata.
1719       *
1720       * @since 1.24
1721       * @param ParserOutput $parserOutput
1722       */
1723  	public function addParserOutputText( $parserOutput ) {
1724          $text = $parserOutput->getText();
1725          wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
1726          $this->addHTML( $text );
1727      }
1728  
1729      /**
1730       * Add everything from a ParserOutput object.
1731       *
1732       * @param ParserOutput $parserOutput
1733       */
1734  	function addParserOutput( $parserOutput ) {
1735          $this->addParserOutputMetadata( $parserOutput );
1736          $parserOutput->setTOCEnabled( $this->mEnableTOC );
1737  
1738          // Touch section edit links only if not previously disabled
1739          if ( $parserOutput->getEditSectionTokens() ) {
1740              $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks );
1741          }
1742  
1743          $this->addParserOutputText( $parserOutput );
1744      }
1745  
1746      /**
1747       * Add the output of a QuickTemplate to the output buffer
1748       *
1749       * @param QuickTemplate $template
1750       */
1751  	public function addTemplate( &$template ) {
1752          $this->addHTML( $template->getHTML() );
1753      }
1754  
1755      /**
1756       * Parse wikitext and return the HTML.
1757       *
1758       * @param string $text
1759       * @param bool $linestart Is this the start of a line?
1760       * @param bool $interface Use interface language ($wgLang instead of
1761       *   $wgContLang) while parsing language sensitive magic words like GRAMMAR and PLURAL.
1762       *   This also disables LanguageConverter.
1763       * @param Language $language Target language object, will override $interface
1764       * @throws MWException
1765       * @return string HTML
1766       */
1767  	public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1768          global $wgParser;
1769  
1770          if ( is_null( $this->getTitle() ) ) {
1771              throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
1772          }
1773  
1774          $popts = $this->parserOptions();
1775          if ( $interface ) {
1776              $popts->setInterfaceMessage( true );
1777          }
1778          if ( $language !== null ) {
1779              $oldLang = $popts->setTargetLanguage( $language );
1780          }
1781  
1782          $parserOutput = $wgParser->getFreshParser()->parse(
1783              $text, $this->getTitle(), $popts,
1784              $linestart, true, $this->mRevisionId
1785          );
1786  
1787          if ( $interface ) {
1788              $popts->setInterfaceMessage( false );
1789          }
1790          if ( $language !== null ) {
1791              $popts->setTargetLanguage( $oldLang );
1792          }
1793  
1794          return $parserOutput->getText();
1795      }
1796  
1797      /**
1798       * Parse wikitext, strip paragraphs, and return the HTML.
1799       *
1800       * @param string $text
1801       * @param bool $linestart Is this the start of a line?
1802       * @param bool $interface Use interface language ($wgLang instead of
1803       *   $wgContLang) while parsing language sensitive magic
1804       *   words like GRAMMAR and PLURAL
1805       * @return string HTML
1806       */
1807  	public function parseInline( $text, $linestart = true, $interface = false ) {
1808          $parsed = $this->parse( $text, $linestart, $interface );
1809          return Parser::stripOuterParagraph( $parsed );
1810      }
1811  
1812      /**
1813       * Set the value of the "s-maxage" part of the "Cache-control" HTTP header
1814       *
1815       * @param int $maxage Maximum cache time on the Squid, in seconds.
1816       */
1817  	public function setSquidMaxage( $maxage ) {
1818          $this->mSquidMaxage = $maxage;
1819      }
1820  
1821      /**
1822       * Use enableClientCache(false) to force it to send nocache headers
1823       *
1824       * @param bool $state
1825       *
1826       * @return bool
1827       */
1828  	public function enableClientCache( $state ) {
1829          return wfSetVar( $this->mEnableClientCache, $state );
1830      }
1831  
1832      /**
1833       * Get the list of cookies that will influence on the cache
1834       *
1835       * @return array
1836       */
1837  	function getCacheVaryCookies() {
1838          static $cookies;
1839          if ( $cookies === null ) {
1840              $config = $this->getConfig();
1841              $cookies = array_merge(
1842                  array(
1843                      $config->get( 'CookiePrefix' ) . 'Token',
1844                      $config->get( 'CookiePrefix' ) . 'LoggedOut',
1845                      "forceHTTPS",
1846                      session_name()
1847                  ),
1848                  $config->get( 'CacheVaryCookies' )
1849              );
1850              wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) );
1851          }
1852          return $cookies;
1853      }
1854  
1855      /**
1856       * Check if the request has a cache-varying cookie header
1857       * If it does, it's very important that we don't allow public caching
1858       *
1859       * @return bool
1860       */
1861  	function haveCacheVaryCookies() {
1862          $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
1863          if ( $cookieHeader === false ) {
1864              return false;
1865          }
1866          $cvCookies = $this->getCacheVaryCookies();
1867          foreach ( $cvCookies as $cookieName ) {
1868              # Check for a simple string match, like the way squid does it
1869              if ( strpos( $cookieHeader, $cookieName ) !== false ) {
1870                  wfDebug( __METHOD__ . ": found $cookieName\n" );
1871                  return true;
1872              }
1873          }
1874          wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
1875          return false;
1876      }
1877  
1878      /**
1879       * Add an HTTP header that will influence on the cache
1880       *
1881       * @param string $header Header name
1882       * @param array|null $option
1883       * @todo FIXME: Document the $option parameter; it appears to be for
1884       *        X-Vary-Options but what format is acceptable?
1885       */
1886  	public function addVaryHeader( $header, $option = null ) {
1887          if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
1888              $this->mVaryHeader[$header] = (array)$option;
1889          } elseif ( is_array( $option ) ) {
1890              if ( is_array( $this->mVaryHeader[$header] ) ) {
1891                  $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
1892              } else {
1893                  $this->mVaryHeader[$header] = $option;
1894              }
1895          }
1896          $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] );
1897      }
1898  
1899      /**
1900       * Return a Vary: header on which to vary caches. Based on the keys of $mVaryHeader,
1901       * such as Accept-Encoding or Cookie
1902       *
1903       * @return string
1904       */
1905  	public function getVaryHeader() {
1906          return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) );
1907      }
1908  
1909      /**
1910       * Get a complete X-Vary-Options header
1911       *
1912       * @return string
1913       */
1914  	public function getXVO() {
1915          $cvCookies = $this->getCacheVaryCookies();
1916  
1917          $cookiesOption = array();
1918          foreach ( $cvCookies as $cookieName ) {
1919              $cookiesOption[] = 'string-contains=' . $cookieName;
1920          }
1921          $this->addVaryHeader( 'Cookie', $cookiesOption );
1922  
1923          $headers = array();
1924          foreach ( $this->mVaryHeader as $header => $option ) {
1925              $newheader = $header;
1926              if ( is_array( $option ) && count( $option ) > 0 ) {
1927                  $newheader .= ';' . implode( ';', $option );
1928              }
1929              $headers[] = $newheader;
1930          }
1931          $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
1932  
1933          return $xvo;
1934      }
1935  
1936      /**
1937       * bug 21672: Add Accept-Language to Vary and XVO headers
1938       * if there's no 'variant' parameter existed in GET.
1939       *
1940       * For example:
1941       *   /w/index.php?title=Main_page should always be served; but
1942       *   /w/index.php?title=Main_page&variant=zh-cn should never be served.
1943       */
1944  	function addAcceptLanguage() {
1945          $title = $this->getTitle();
1946          if ( !$title instanceof Title ) {
1947              return;
1948          }
1949  
1950          $lang = $title->getPageLanguage();
1951          if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
1952              $variants = $lang->getVariants();
1953              $aloption = array();
1954              foreach ( $variants as $variant ) {
1955                  if ( $variant === $lang->getCode() ) {
1956                      continue;
1957                  } else {
1958                      $aloption[] = 'string-contains=' . $variant;
1959  
1960                      // IE and some other browsers use BCP 47 standards in
1961                      // their Accept-Language header, like "zh-CN" or "zh-Hant".
1962                      // We should handle these too.
1963                      $variantBCP47 = wfBCP47( $variant );
1964                      if ( $variantBCP47 !== $variant ) {
1965                          $aloption[] = 'string-contains=' . $variantBCP47;
1966                      }
1967                  }
1968              }
1969              $this->addVaryHeader( 'Accept-Language', $aloption );
1970          }
1971      }
1972  
1973      /**
1974       * Set a flag which will cause an X-Frame-Options header appropriate for
1975       * edit pages to be sent. The header value is controlled by
1976       * $wgEditPageFrameOptions.
1977       *
1978       * This is the default for special pages. If you display a CSRF-protected
1979       * form on an ordinary view page, then you need to call this function.
1980       *
1981       * @param bool $enable
1982       */
1983  	public function preventClickjacking( $enable = true ) {
1984          $this->mPreventClickjacking = $enable;
1985      }
1986  
1987      /**
1988       * Turn off frame-breaking. Alias for $this->preventClickjacking(false).
1989       * This can be called from pages which do not contain any CSRF-protected
1990       * HTML form.
1991       */
1992  	public function allowClickjacking() {
1993          $this->mPreventClickjacking = false;
1994      }
1995  
1996      /**
1997       * Get the prevent-clickjacking flag
1998       *
1999       * @since 1.24
2000       * @return bool
2001       */
2002  	public function getPreventClickjacking() {
2003          return $this->mPreventClickjacking;
2004      }
2005  
2006      /**
2007       * Get the X-Frame-Options header value (without the name part), or false
2008       * if there isn't one. This is used by Skin to determine whether to enable
2009       * JavaScript frame-breaking, for clients that don't support X-Frame-Options.
2010       *
2011       * @return string
2012       */
2013  	public function getFrameOptions() {
2014          $config = $this->getConfig();
2015          if ( $config->get( 'BreakFrames' ) ) {
2016              return 'DENY';
2017          } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2018              return $config->get( 'EditPageFrameOptions' );
2019          }
2020          return false;
2021      }
2022  
2023      /**
2024       * Send cache control HTTP headers
2025       */
2026  	public function sendCacheControl() {
2027          $response = $this->getRequest()->response();
2028          $config = $this->getConfig();
2029          if ( $config->get( 'UseETag' ) && $this->mETag ) {
2030              $response->header( "ETag: $this->mETag" );
2031          }
2032  
2033          $this->addVaryHeader( 'Cookie' );
2034          $this->addAcceptLanguage();
2035  
2036          # don't serve compressed data to clients who can't handle it
2037          # maintain different caches for logged-in users and non-logged in ones
2038          $response->header( $this->getVaryHeader() );
2039  
2040          if ( $config->get( 'UseXVO' ) ) {
2041              # Add an X-Vary-Options header for Squid with Wikimedia patches
2042              $response->header( $this->getXVO() );
2043          }
2044  
2045          if ( $this->mEnableClientCache ) {
2046              if (
2047                  $config->get( 'UseSquid' ) && session_id() == '' && !$this->isPrintable() &&
2048                  $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies()
2049              ) {
2050                  if ( $config->get( 'UseESI' ) ) {
2051                      # We'll purge the proxy cache explicitly, but require end user agents
2052                      # to revalidate against the proxy on each visit.
2053                      # Surrogate-Control controls our Squid, Cache-Control downstream caches
2054                      wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", 'log' );
2055                      # start with a shorter timeout for initial testing
2056                      # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2057                      $response->header( 'Surrogate-Control: max-age=' . $config->get( 'SquidMaxage' )
2058                          . '+' . $this->mSquidMaxage . ', content="ESI/1.0"' );
2059                      $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2060                  } else {
2061                      # We'll purge the proxy cache for anons explicitly, but require end user agents
2062                      # to revalidate against the proxy on each visit.
2063                      # IMPORTANT! The Squid needs to replace the Cache-Control header with
2064                      # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2065                      wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", 'log' );
2066                      # start with a shorter timeout for initial testing
2067                      # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2068                      $response->header( 'Cache-Control: s-maxage=' . $this->mSquidMaxage
2069                          . ', must-revalidate, max-age=0' );
2070                  }
2071              } else {
2072                  # We do want clients to cache if they can, but they *must* check for updates
2073                  # on revisiting the page.
2074                  wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", 'log' );
2075                  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2076                  $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2077              }
2078              if ( $this->mLastModified ) {
2079                  $response->header( "Last-Modified: {$this->mLastModified}" );
2080              }
2081          } else {
2082              wfDebug( __METHOD__ . ": no caching **\n", 'log' );
2083  
2084              # In general, the absence of a last modified header should be enough to prevent
2085              # the client from using its cache. We send a few other things just to make sure.
2086              $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2087              $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2088              $response->header( 'Pragma: no-cache' );
2089          }
2090      }
2091  
2092      /**
2093       * Finally, all the text has been munged and accumulated into
2094       * the object, let's actually output it:
2095       */
2096  	public function output() {
2097          global $wgLanguageCode;
2098  
2099          if ( $this->mDoNothing ) {
2100              return;
2101          }
2102  
2103          wfProfileIn( __METHOD__ );
2104  
2105          $response = $this->getRequest()->response();
2106          $config = $this->getConfig();
2107  
2108          if ( $this->mRedirect != '' ) {
2109              # Standards require redirect URLs to be absolute
2110              $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2111  
2112              $redirect = $this->mRedirect;
2113              $code = $this->mRedirectCode;
2114  
2115              if ( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
2116                  if ( $code == '301' || $code == '303' ) {
2117                      if ( !$config->get( 'DebugRedirects' ) ) {
2118                          $message = HttpStatus::getMessage( $code );
2119                          $response->header( "HTTP/1.1 $code $message" );
2120                      }
2121                      $this->mLastModified = wfTimestamp( TS_RFC2822 );
2122                  }
2123                  if ( $config->get( 'VaryOnXFP' ) ) {
2124                      $this->addVaryHeader( 'X-Forwarded-Proto' );
2125                  }
2126                  $this->sendCacheControl();
2127  
2128                  $response->header( "Content-Type: text/html; charset=utf-8" );
2129                  if ( $config->get( 'DebugRedirects' ) ) {
2130                      $url = htmlspecialchars( $redirect );
2131                      print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2132                      print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2133                      print "</body>\n</html>\n";
2134                  } else {
2135                      $response->header( 'Location: ' . $redirect );
2136                  }
2137              }
2138  
2139              wfProfileOut( __METHOD__ );
2140              return;
2141          } elseif ( $this->mStatusCode ) {
2142              $message = HttpStatus::getMessage( $this->mStatusCode );
2143              if ( $message ) {
2144                  $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
2145              }
2146          }
2147  
2148          # Buffer output; final headers may depend on later processing
2149          ob_start();
2150  
2151          $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2152          $response->header( 'Content-language: ' . $wgLanguageCode );
2153  
2154          // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
2155          // jQuery etc. can work correctly.
2156          $response->header( 'X-UA-Compatible: IE=Edge' );
2157  
2158          // Prevent framing, if requested
2159          $frameOptions = $this->getFrameOptions();
2160          if ( $frameOptions ) {
2161              $response->header( "X-Frame-Options: $frameOptions" );
2162          }
2163  
2164          if ( $this->mArticleBodyOnly ) {
2165              echo $this->mBodytext;
2166          } else {
2167  
2168              $sk = $this->getSkin();
2169              // add skin specific modules
2170              $modules = $sk->getDefaultModules();
2171  
2172              // enforce various default modules for all skins
2173              $coreModules = array(
2174                  // keep this list as small as possible
2175                  'mediawiki.page.startup',
2176                  'mediawiki.user',
2177              );
2178  
2179              // Support for high-density display images if enabled
2180              if ( $config->get( 'ResponsiveImages' ) ) {
2181                  $coreModules[] = 'mediawiki.hidpi';
2182              }
2183  
2184              $this->addModules( $coreModules );
2185              foreach ( $modules as $group ) {
2186                  $this->addModules( $group );
2187              }
2188              MWDebug::addModules( $this );
2189  
2190              // Hook that allows last minute changes to the output page, e.g.
2191              // adding of CSS or Javascript by extensions.
2192              wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
2193  
2194              wfProfileIn( 'Output-skin' );
2195              $sk->outputPage();
2196              wfProfileOut( 'Output-skin' );
2197          }
2198  
2199          // This hook allows last minute changes to final overall output by modifying output buffer
2200          wfRunHooks( 'AfterFinalPageOutput', array( $this ) );
2201  
2202          $this->sendCacheControl();
2203  
2204          ob_end_flush();
2205  
2206          wfProfileOut( __METHOD__ );
2207      }
2208  
2209      /**
2210       * Actually output something with print.
2211       *
2212       * @param string $ins The string to output
2213       * @deprecated since 1.22 Use echo yourself.
2214       */
2215  	public function out( $ins ) {
2216          wfDeprecated( __METHOD__, '1.22' );
2217          print $ins;
2218      }
2219  
2220      /**
2221       * Produce a "user is blocked" page.
2222       * @deprecated since 1.18
2223       */
2224  	function blockedPage() {
2225          throw new UserBlockedError( $this->getUser()->mBlock );
2226      }
2227  
2228      /**
2229       * Prepare this object to display an error page; disable caching and
2230       * indexing, clear the current text and redirect, set the page's title
2231       * and optionally an custom HTML title (content of the "<title>" tag).
2232       *
2233       * @param string|Message $pageTitle Will be passed directly to setPageTitle()
2234       * @param string|Message $htmlTitle Will be passed directly to setHTMLTitle();
2235       *                   optional, if not passed the "<title>" attribute will be
2236       *                   based on $pageTitle
2237       */
2238  	public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2239          $this->setPageTitle( $pageTitle );
2240          if ( $htmlTitle !== false ) {
2241              $this->setHTMLTitle( $htmlTitle );
2242          }
2243          $this->setRobotPolicy( 'noindex,nofollow' );
2244          $this->setArticleRelated( false );
2245          $this->enableClientCache( false );
2246          $this->mRedirect = '';
2247          $this->clearSubtitle();
2248          $this->clearHTML();
2249      }
2250  
2251      /**
2252       * Output a standard error page
2253       *
2254       * showErrorPage( 'titlemsg', 'pagetextmsg' );
2255       * showErrorPage( 'titlemsg', 'pagetextmsg', array( 'param1', 'param2' ) );
2256       * showErrorPage( 'titlemsg', $messageObject );
2257       * showErrorPage( $titleMessageObject, $messageObject );
2258       *
2259       * @param string|Message $title Message key (string) for page title, or a Message object
2260       * @param string|Message $msg Message key (string) for page text, or a Message object
2261       * @param array $params Message parameters; ignored if $msg is a Message object
2262       */
2263  	public function showErrorPage( $title, $msg, $params = array() ) {
2264          if ( !$title instanceof Message ) {
2265              $title = $this->msg( $title );
2266          }
2267  
2268          $this->prepareErrorPage( $title );
2269  
2270          if ( $msg instanceof Message ) {
2271              if ( $params !== array() ) {
2272                  trigger_error( 'Argument ignored: $params. The message parameters argument '
2273                      . 'is discarded when the $msg argument is a Message object instead of '
2274                      . 'a string.', E_USER_NOTICE );
2275              }
2276              $this->addHTML( $msg->parseAsBlock() );
2277          } else {
2278              $this->addWikiMsgArray( $msg, $params );
2279          }
2280  
2281          $this->returnToMain();
2282      }
2283  
2284      /**
2285       * Output a standard permission error page
2286       *
2287       * @param array $errors Error message keys
2288       * @param string $action Action that was denied or null if unknown
2289       */
2290  	public function showPermissionsErrorPage( array $errors, $action = null ) {
2291          // For some action (read, edit, create and upload), display a "login to do this action"
2292          // error if all of the following conditions are met:
2293          // 1. the user is not logged in
2294          // 2. the only error is insufficient permissions (i.e. no block or something else)
2295          // 3. the error can be avoided simply by logging in
2296          if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) )
2297              && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2298              && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2299              && ( User::groupHasPermission( 'user', $action )
2300              || User::groupHasPermission( 'autoconfirmed', $action ) )
2301          ) {
2302              $displayReturnto = null;
2303  
2304              # Due to bug 32276, if a user does not have read permissions,
2305              # $this->getTitle() will just give Special:Badtitle, which is
2306              # not especially useful as a returnto parameter. Use the title
2307              # from the request instead, if there was one.
2308              $request = $this->getRequest();
2309              $returnto = Title::newFromURL( $request->getVal( 'title', '' ) );
2310              if ( $action == 'edit' ) {
2311                  $msg = 'whitelistedittext';
2312                  $displayReturnto = $returnto;
2313              } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2314                  $msg = 'nocreatetext';
2315              } elseif ( $action == 'upload' ) {
2316                  $msg = 'uploadnologintext';
2317              } else { # Read
2318                  $msg = 'loginreqpagetext';
2319                  $displayReturnto = Title::newMainPage();
2320              }
2321  
2322              $query = array();
2323  
2324              if ( $returnto ) {
2325                  $query['returnto'] = $returnto->getPrefixedText();
2326  
2327                  if ( !$request->wasPosted() ) {
2328                      $returntoquery = $request->getValues();
2329                      unset( $returntoquery['title'] );
2330                      unset( $returntoquery['returnto'] );
2331                      unset( $returntoquery['returntoquery'] );
2332                      $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2333                  }
2334              }
2335              $loginLink = Linker::linkKnown(
2336                  SpecialPage::getTitleFor( 'Userlogin' ),
2337                  $this->msg( 'loginreqlink' )->escaped(),
2338                  array(),
2339                  $query
2340              );
2341  
2342              $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2343              $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
2344  
2345              # Don't return to a page the user can't read otherwise
2346              # we'll end up in a pointless loop
2347              if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2348                  $this->returnToMain( null, $displayReturnto );
2349              }
2350          } else {
2351              $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2352              $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
2353          }
2354      }
2355  
2356      /**
2357       * Display an error page indicating that a given version of MediaWiki is
2358       * required to use it
2359       *
2360       * @param mixed $version The version of MediaWiki needed to use the page
2361       */
2362  	public function versionRequired( $version ) {
2363          $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2364  
2365          $this->addWikiMsg( 'versionrequiredtext', $version );
2366          $this->returnToMain();
2367      }
2368  
2369      /**
2370       * Display an error page noting that a given permission bit is required.
2371       * @deprecated since 1.18, just throw the exception directly
2372       * @param string $permission Key required
2373       * @throws PermissionsError
2374       */
2375  	public function permissionRequired( $permission ) {
2376          throw new PermissionsError( $permission );
2377      }
2378  
2379      /**
2380       * Produce the stock "please login to use the wiki" page
2381       *
2382       * @deprecated since 1.19; throw the exception directly
2383       */
2384  	public function loginToUse() {
2385          throw new PermissionsError( 'read' );
2386      }
2387  
2388      /**
2389       * Format a list of error messages
2390       *
2391       * @param array $errors Array of arrays returned by Title::getUserPermissionsErrors
2392       * @param string $action Action that was denied or null if unknown
2393       * @return string The wikitext error-messages, formatted into a list.
2394       */
2395  	public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2396          if ( $action == null ) {
2397              $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2398          } else {
2399              $action_desc = $this->msg( "action-$action" )->plain();
2400              $text = $this->msg(
2401                  'permissionserrorstext-withaction',
2402                  count( $errors ),
2403                  $action_desc
2404              )->plain() . "\n\n";
2405          }
2406  
2407          if ( count( $errors ) > 1 ) {
2408              $text .= '<ul class="permissions-errors">' . "\n";
2409  
2410              foreach ( $errors as $error ) {
2411                  $text .= '<li>';
2412                  $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain();
2413                  $text .= "</li>\n";
2414              }
2415              $text .= '</ul>';
2416          } else {
2417              $text .= "<div class=\"permissions-errors\">\n" .
2418                      call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() .
2419                      "\n</div>";
2420          }
2421  
2422          return $text;
2423      }
2424  
2425      /**
2426       * Display a page stating that the Wiki is in read-only mode,
2427       * and optionally show the source of the page that the user
2428       * was trying to edit.  Should only be called (for this
2429       * purpose) after wfReadOnly() has returned true.
2430       *
2431       * For historical reasons, this function is _also_ used to
2432       * show the error message when a user tries to edit a page
2433       * they are not allowed to edit.  (Unless it's because they're
2434       * blocked, then we show blockedPage() instead.)  In this
2435       * case, the second parameter should be set to true and a list
2436       * of reasons supplied as the third parameter.
2437       *
2438       * @todo Needs to be split into multiple functions.
2439       *
2440       * @param string $source Source code to show (or null).
2441       * @param bool $protected Is this a permissions error?
2442       * @param array $reasons List of reasons for this error, as returned by
2443       *   Title::getUserPermissionsErrors().
2444       * @param string $action Action that was denied or null if unknown
2445       * @throws ReadOnlyError
2446       */
2447  	public function readOnlyPage( $source = null, $protected = false,
2448          array $reasons = array(), $action = null
2449      ) {
2450          $this->setRobotPolicy( 'noindex,nofollow' );
2451          $this->setArticleRelated( false );
2452  
2453          // If no reason is given, just supply a default "I can't let you do
2454          // that, Dave" message.  Should only occur if called by legacy code.
2455          if ( $protected && empty( $reasons ) ) {
2456              $reasons[] = array( 'badaccess-group0' );
2457          }
2458  
2459          if ( !empty( $reasons ) ) {
2460              // Permissions error
2461              if ( $source ) {
2462                  $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) );
2463                  $this->addBacklinkSubtitle( $this->getTitle() );
2464              } else {
2465                  $this->setPageTitle( $this->msg( 'badaccess' ) );
2466              }
2467              $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
2468          } else {
2469              // Wiki is read only
2470              throw new ReadOnlyError;
2471          }
2472  
2473          // Show source, if supplied
2474          if ( is_string( $source ) ) {
2475              $this->addWikiMsg( 'viewsourcetext' );
2476  
2477              $pageLang = $this->getTitle()->getPageLanguage();
2478              $params = array(
2479                  'id' => 'wpTextbox1',
2480                  'name' => 'wpTextbox1',
2481                  'cols' => $this->getUser()->getOption( 'cols' ),
2482                  'rows' => $this->getUser()->getOption( 'rows' ),
2483                  'readonly' => 'readonly',
2484                  'lang' => $pageLang->getHtmlCode(),
2485                  'dir' => $pageLang->getDir(),
2486              );
2487              $this->addHTML( Html::element( 'textarea', $params, $source ) );
2488  
2489              // Show templates used by this article
2490              $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() );
2491              $this->addHTML( "<div class='templatesUsed'>
2492  $templates
2493  </div>
2494  " );
2495          }
2496  
2497          # If the title doesn't exist, it's fairly pointless to print a return
2498          # link to it.  After all, you just tried editing it and couldn't, so
2499          # what's there to do there?
2500          if ( $this->getTitle()->exists() ) {
2501              $this->returnToMain( null, $this->getTitle() );
2502          }
2503      }
2504  
2505      /**
2506       * Turn off regular page output and return an error response
2507       * for when rate limiting has triggered.
2508       */
2509  	public function rateLimited() {
2510          throw new ThrottledError;
2511      }
2512  
2513      /**
2514       * Show a warning about slave lag
2515       *
2516       * If the lag is higher than $wgSlaveLagCritical seconds,
2517       * then the warning is a bit more obvious. If the lag is
2518       * lower than $wgSlaveLagWarning, then no warning is shown.
2519       *
2520       * @param int $lag Slave lag
2521       */
2522  	public function showLagWarning( $lag ) {
2523          $config = $this->getConfig();
2524          if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2525              $message = $lag < $config->get( 'SlaveLagCritical' )
2526                  ? 'lag-warn-normal'
2527                  : 'lag-warn-high';
2528              $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
2529              $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) );
2530          }
2531      }
2532  
2533  	public function showFatalError( $message ) {
2534          $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2535  
2536          $this->addHTML( $message );
2537      }
2538  
2539  	public function showUnexpectedValueError( $name, $val ) {
2540          $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
2541      }
2542  
2543  	public function showFileCopyError( $old, $new ) {
2544          $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
2545      }
2546  
2547  	public function showFileRenameError( $old, $new ) {
2548          $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
2549      }
2550  
2551  	public function showFileDeleteError( $name ) {
2552          $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
2553      }
2554  
2555  	public function showFileNotFoundError( $name ) {
2556          $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
2557      }
2558  
2559      /**
2560       * Add a "return to" link pointing to a specified title
2561       *
2562       * @param Title $title Title to link
2563       * @param array $query Query string parameters
2564       * @param string $text Text of the link (input is not escaped)
2565       * @param array $options Options array to pass to Linker
2566       */
2567  	public function addReturnTo( $title, array $query = array(), $text = null, $options = array() ) {
2568          $link = $this->msg( 'returnto' )->rawParams(
2569              Linker::link( $title, $text, array(), $query, $options ) )->escaped();
2570          $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2571      }
2572  
2573      /**
2574       * Add a "return to" link pointing to a specified title,
2575       * or the title indicated in the request, or else the main page
2576       *
2577       * @param mixed $unused
2578       * @param Title|string $returnto Title or String to return to
2579       * @param string $returntoquery Query string for the return to link
2580       */
2581  	public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2582          if ( $returnto == null ) {
2583              $returnto = $this->getRequest()->getText( 'returnto' );
2584          }
2585  
2586          if ( $returntoquery == null ) {
2587              $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2588          }
2589  
2590          if ( $returnto === '' ) {
2591              $returnto = Title::newMainPage();
2592          }
2593  
2594          if ( is_object( $returnto ) ) {
2595              $titleObj = $returnto;
2596          } else {
2597              $titleObj = Title::newFromText( $returnto );
2598          }
2599          if ( !is_object( $titleObj ) ) {
2600              $titleObj = Title::newMainPage();
2601          }
2602  
2603          $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2604      }
2605  
2606      /**
2607       * @param Skin $sk The given Skin
2608       * @param bool $includeStyle Unused
2609       * @return string The doctype, opening "<html>", and head element.
2610       */
2611  	public function headElement( Skin $sk, $includeStyle = true ) {
2612          global $wgContLang;
2613  
2614          $userdir = $this->getLanguage()->getDir();
2615          $sitedir = $wgContLang->getDir();
2616  
2617          $ret = Html::htmlHeader( $sk->getHtmlElementAttributes() );
2618  
2619          if ( $this->getHTMLTitle() == '' ) {
2620              $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
2621          }
2622  
2623          $openHead = Html::openElement( 'head' );
2624          if ( $openHead ) {
2625              # Don't bother with the newline if $head == ''
2626              $ret .= "$openHead\n";
2627          }
2628  
2629          if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
2630              // Add <meta charset="UTF-8">
2631              // This should be before <title> since it defines the charset used by
2632              // text including the text inside <title>.
2633              // The spec recommends defining XHTML5's charset using the XML declaration
2634              // instead of meta.
2635              // Our XML declaration is output by Html::htmlHeader.
2636              // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type
2637              // http://www.whatwg.org/html/semantics.html#charset
2638              $ret .= Html::element( 'meta', array( 'charset' => 'UTF-8' ) ) . "\n";
2639          }
2640  
2641          $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
2642  
2643          foreach ( $this->getHeadLinksArray() as $item ) {
2644              $ret .= $item . "\n";
2645          }
2646  
2647          // No newline after buildCssLinks since makeResourceLoaderLink did that already
2648          $ret .= $this->buildCssLinks();
2649  
2650          $ret .= $this->getHeadScripts() . "\n";
2651  
2652          foreach ( $this->mHeadItems as $item ) {
2653              $ret .= $item . "\n";
2654          }
2655  
2656          $closeHead = Html::closeElement( 'head' );
2657          if ( $closeHead ) {
2658              $ret .= "$closeHead\n";
2659          }
2660  
2661          $bodyClasses = array();
2662          $bodyClasses[] = 'mediawiki';
2663  
2664          # Classes for LTR/RTL directionality support
2665          $bodyClasses[] = $userdir;
2666          $bodyClasses[] = "sitedir-$sitedir";
2667  
2668          if ( $this->getLanguage()->capitalizeAllNouns() ) {
2669              # A <body> class is probably not the best way to do this . . .
2670              $bodyClasses[] = 'capitalize-all-nouns';
2671          }
2672  
2673          $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
2674          $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
2675          $bodyClasses[] =
2676              'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
2677  
2678          $bodyAttrs = array();
2679          // While the implode() is not strictly needed, it's used for backwards compatibility
2680          // (this used to be built as a string and hooks likely still expect that).
2681          $bodyAttrs['class'] = implode( ' ', $bodyClasses );
2682  
2683          // Allow skins and extensions to add body attributes they need
2684          $sk->addToBodyAttributes( $this, $bodyAttrs );
2685          wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
2686  
2687          $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
2688  
2689          return $ret;
2690      }
2691  
2692      /**
2693       * Get a ResourceLoader object associated with this OutputPage
2694       *
2695       * @return ResourceLoader
2696       */
2697  	public function getResourceLoader() {
2698          if ( is_null( $this->mResourceLoader ) ) {
2699              $this->mResourceLoader = new ResourceLoader( $this->getConfig() );
2700          }
2701          return $this->mResourceLoader;
2702      }
2703  
2704      /**
2705       * @todo Document
2706       * @param array|string $modules One or more module names
2707       * @param string $only ResourceLoaderModule TYPE_ class constant
2708       * @param bool $useESI
2709       * @param array $extraQuery Array with extra query parameters to add to each
2710       *   request. array( param => value ).
2711       * @param bool $loadCall If true, output an (asynchronous) mw.loader.load()
2712       *   call rather than a "<script src='...'>" tag.
2713       * @return string The html "<script>", "<link>" and "<style>" tags
2714       */
2715  	protected function makeResourceLoaderLink( $modules, $only, $useESI = false,
2716          array $extraQuery = array(), $loadCall = false
2717      ) {
2718          $modules = (array)$modules;
2719  
2720          $links = array(
2721              'html' => '',
2722              'states' => array(),
2723          );
2724  
2725          if ( !count( $modules ) ) {
2726              return $links;
2727          }
2728  
2729          if ( count( $modules ) > 1 ) {
2730              // Remove duplicate module requests
2731              $modules = array_unique( $modules );
2732              // Sort module names so requests are more uniform
2733              sort( $modules );
2734  
2735              if ( ResourceLoader::inDebugMode() ) {
2736                  // Recursively call us for every item
2737                  foreach ( $modules as $name ) {
2738                      $link = $this->makeResourceLoaderLink( $name, $only, $useESI );
2739                      $links['html'] .= $link['html'];
2740                      $links['states'] += $link['states'];
2741                  }
2742                  return $links;
2743              }
2744          }
2745  
2746          if ( !is_null( $this->mTarget ) ) {
2747              $extraQuery['target'] = $this->mTarget;
2748          }
2749  
2750          // Create keyed-by-source and then keyed-by-group list of module objects from modules list
2751          $sortedModules = array();
2752          $resourceLoader = $this->getResourceLoader();
2753          $resourceLoaderUseESI = $this->getConfig()->get( 'ResourceLoaderUseESI' );
2754          foreach ( $modules as $name ) {
2755              $module = $resourceLoader->getModule( $name );
2756              # Check that we're allowed to include this module on this page
2757              if ( !$module
2758                  || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS )
2759                      && $only == ResourceLoaderModule::TYPE_SCRIPTS )
2760                  || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
2761                      && $only == ResourceLoaderModule::TYPE_STYLES )
2762                  || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
2763                      && $only == ResourceLoaderModule::TYPE_COMBINED )
2764                  || ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) )
2765              ) {
2766                  continue;
2767              }
2768  
2769              $sortedModules[$module->getSource()][$module->getGroup()][$name] = $module;
2770          }
2771  
2772          foreach ( $sortedModules as $source => $groups ) {
2773              foreach ( $groups as $group => $grpModules ) {
2774                  // Special handling for user-specific groups
2775                  $user = null;
2776                  if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
2777                      $user = $this->getUser()->getName();
2778                  }
2779  
2780                  // Create a fake request based on the one we are about to make so modules return
2781                  // correct timestamp and emptiness data
2782                  $query = ResourceLoader::makeLoaderQuery(
2783                      array(), // modules; not determined yet
2784                      $this->getLanguage()->getCode(),
2785                      $this->getSkin()->getSkinName(),
2786                      $user,
2787                      null, // version; not determined yet
2788                      ResourceLoader::inDebugMode(),
2789                      $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
2790                      $this->isPrintable(),
2791                      $this->getRequest()->getBool( 'handheld' ),
2792                      $extraQuery
2793                  );
2794                  $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
2795  
2796  
2797                  // Extract modules that know they're empty and see if we have one or more
2798                  // raw modules
2799                  $isRaw = false;
2800                  foreach ( $grpModules as $key => $module ) {
2801                      // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857)
2802                      // If we're only getting the styles, we don't need to do anything for empty modules.
2803                      if ( $module->isKnownEmpty( $context ) ) {
2804                          unset( $grpModules[$key] );
2805                          if ( $only !== ResourceLoaderModule::TYPE_STYLES ) {
2806                              $links['states'][$key] = 'ready';
2807                          }
2808                      }
2809  
2810                      $isRaw |= $module->isRaw();
2811                  }
2812  
2813                  // If there are no non-empty modules, skip this group
2814                  if ( count( $grpModules ) === 0 ) {
2815                      continue;
2816                  }
2817  
2818                  // Inline private modules. These can't be loaded through load.php for security
2819                  // reasons, see bug 34907. Note that these modules should be loaded from
2820                  // getHeadScripts() before the first loader call. Otherwise other modules can't
2821                  // properly use them as dependencies (bug 30914)
2822                  if ( $group === 'private' ) {
2823                      if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
2824                          $links['html'] .= Html::inlineStyle(
2825                              $resourceLoader->makeModuleResponse( $context, $grpModules )
2826                          );
2827                      } else {
2828                          $links['html'] .= Html::inlineScript(
2829                              ResourceLoader::makeLoaderConditionalScript(
2830                                  $resourceLoader->makeModuleResponse( $context, $grpModules )
2831                              )
2832                          );
2833                      }
2834                      $links['html'] .= "\n";
2835                      continue;
2836                  }
2837  
2838                  // Special handling for the user group; because users might change their stuff
2839                  // on-wiki like user pages, or user preferences; we need to find the highest
2840                  // timestamp of these user-changeable modules so we can ensure cache misses on change
2841                  // This should NOT be done for the site group (bug 27564) because anons get that too
2842                  // and we shouldn't be putting timestamps in Squid-cached HTML
2843                  $version = null;
2844                  if ( $group === 'user' ) {
2845                      // Get the maximum timestamp
2846                      $timestamp = 1;
2847                      foreach ( $grpModules as $module ) {
2848                          $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
2849                      }
2850                      // Add a version parameter so cache will break when things change
2851                      $query['version'] = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
2852                  }
2853  
2854                  $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $grpModules ) );
2855                  $moduleContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
2856                  $url = $resourceLoader->createLoaderURL( $source, $moduleContext, $extraQuery );
2857  
2858                  if ( $useESI && $resourceLoaderUseESI ) {
2859                      $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
2860                      if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
2861                          $link = Html::inlineStyle( $esi );
2862                      } else {
2863                          $link = Html::inlineScript( $esi );
2864                      }
2865                  } else {
2866                      // Automatically select style/script elements
2867                      if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
2868                          $link = Html::linkedStyle( $url );
2869                      } elseif ( $loadCall ) {
2870                          $link = Html::inlineScript(
2871                              ResourceLoader::makeLoaderConditionalScript(
2872                                  Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
2873                              )
2874                          );
2875                      } else {
2876                          $link = Html::linkedScript( $url );
2877                          if ( $context->getOnly() === 'scripts' && !$context->getRaw() && !$isRaw ) {
2878                              // Wrap only=script requests in a conditional as browsers not supported
2879                              // by the startup module would unconditionally execute this module.
2880                              // Otherwise users will get "ReferenceError: mw is undefined" or
2881                              // "jQuery is undefined" from e.g. a "site" module.
2882                              $link = Html::inlineScript(
2883                                  ResourceLoader::makeLoaderConditionalScript(
2884                                      Xml::encodeJsCall( 'document.write', array( $link ) )
2885                                  )
2886                              );
2887                          }
2888  
2889                          // For modules requested directly in the html via <link> or <script>,
2890                          // tell mw.loader they are being loading to prevent duplicate requests.
2891                          foreach ( $grpModules as $key => $module ) {
2892                              // Don't output state=loading for the startup module..
2893                              if ( $key !== 'startup' ) {
2894                                  $links['states'][$key] = 'loading';
2895                              }
2896                          }
2897                      }
2898                  }
2899  
2900                  if ( $group == 'noscript' ) {
2901                      $links['html'] .= Html::rawElement( 'noscript', array(), $link ) . "\n";
2902                  } else {
2903                      $links['html'] .= $link . "\n";
2904                  }
2905              }
2906          }
2907  
2908          return $links;
2909      }
2910  
2911      /**
2912       * Build html output from an array of links from makeResourceLoaderLink.
2913       * @param array $links
2914       * @return string HTML
2915       */
2916  	protected static function getHtmlFromLoaderLinks( array $links ) {
2917          $html = '';
2918          $states = array();
2919          foreach ( $links as $link ) {
2920              if ( !is_array( $link ) ) {
2921                  $html .= $link;
2922              } else {
2923                  $html .= $link['html'];
2924                  $states += $link['states'];
2925              }
2926          }
2927  
2928          if ( count( $states ) ) {
2929              $html = Html::inlineScript(
2930                  ResourceLoader::makeLoaderConditionalScript(
2931                      ResourceLoader::makeLoaderStateScript( $states )
2932                  )
2933              ) . "\n" . $html;
2934          }
2935  
2936          return $html;
2937      }
2938  
2939      /**
2940       * JS stuff to put in the "<head>". This is the startup module, config
2941       * vars and modules marked with position 'top'
2942       *
2943       * @return string HTML fragment
2944       */
2945  	function getHeadScripts() {
2946          // Startup - this will immediately load jquery and mediawiki modules
2947          $links = array();
2948          $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
2949  
2950          // Load config before anything else
2951          $links[] = Html::inlineScript(
2952              ResourceLoader::makeLoaderConditionalScript(
2953                  ResourceLoader::makeConfigSetScript( $this->getJSVars() )
2954              )
2955          );
2956  
2957          // Load embeddable private modules before any loader links
2958          // This needs to be TYPE_COMBINED so these modules are properly wrapped
2959          // in mw.loader.implement() calls and deferred until mw.user is available
2960          $embedScripts = array( 'user.options', 'user.tokens' );
2961          $links[] = $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
2962  
2963          // Scripts and messages "only" requests marked for top inclusion
2964          // Messages should go first
2965          $links[] = $this->makeResourceLoaderLink(
2966              $this->getModuleMessages( true, 'top' ),
2967              ResourceLoaderModule::TYPE_MESSAGES
2968          );
2969          $links[] = $this->makeResourceLoaderLink(
2970              $this->getModuleScripts( true, 'top' ),
2971              ResourceLoaderModule::TYPE_SCRIPTS
2972          );
2973  
2974          // Modules requests - let the client calculate dependencies and batch requests as it likes
2975          // Only load modules that have marked themselves for loading at the top
2976          $modules = $this->getModules( true, 'top' );
2977          if ( $modules ) {
2978              $links[] = Html::inlineScript(
2979                  ResourceLoader::makeLoaderConditionalScript(
2980                      Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
2981                  )
2982              );
2983          }
2984  
2985          if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
2986              $links[] = $this->getScriptsForBottomQueue( true );
2987          }
2988  
2989          return self::getHtmlFromLoaderLinks( $links );
2990      }
2991  
2992      /**
2993       * JS stuff to put at the 'bottom', which can either be the bottom of the
2994       * "<body>" or the bottom of the "<head>" depending on
2995       * $wgResourceLoaderExperimentalAsyncLoading: modules marked with position
2996       * 'bottom', legacy scripts ($this->mScripts), user preferences, site JS
2997       * and user JS.
2998       *
2999       * @param bool $inHead If true, this HTML goes into the "<head>",
3000       *   if false it goes into the "<body>".
3001       * @return string
3002       */
3003  	function getScriptsForBottomQueue( $inHead ) {
3004          // Scripts and messages "only" requests marked for bottom inclusion
3005          // If we're in the <head>, use load() calls rather than <script src="..."> tags
3006          // Messages should go first
3007          $links = array();
3008          $links[] = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
3009              ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
3010              /* $loadCall = */ $inHead
3011          );
3012          $links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
3013              ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
3014              /* $loadCall = */ $inHead
3015          );
3016  
3017          // Modules requests - let the client calculate dependencies and batch requests as it likes
3018          // Only load modules that have marked themselves for loading at the bottom
3019          $modules = $this->getModules( true, 'bottom' );
3020          if ( $modules ) {
3021              $links[] = Html::inlineScript(
3022                  ResourceLoader::makeLoaderConditionalScript(
3023                      Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
3024                  )
3025              );
3026          }
3027  
3028          // Legacy Scripts
3029          $links[] = "\n" . $this->mScripts;
3030  
3031          // Add site JS if enabled
3032          $links[] = $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
3033              /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
3034          );
3035  
3036          // Add user JS if enabled
3037          if ( $this->getConfig()->get( 'AllowUserJs' )
3038              && $this->getUser()->isLoggedIn()
3039              && $this->getTitle()
3040              && $this->getTitle()->isJsSubpage()
3041              && $this->userCanPreview()
3042          ) {
3043              # XXX: additional security check/prompt?
3044              // We're on a preview of a JS subpage
3045              // Exclude this page from the user module in case it's in there (bug 26283)
3046              $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
3047                  array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
3048              );
3049              // Load the previewed JS
3050              $links[] = Html::inlineScript( "\n"
3051                      . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
3052  
3053              // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
3054              // asynchronously and may arrive *after* the inline script here. So the previewed code
3055              // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
3056          } else {
3057              // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
3058              $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
3059                  /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
3060              );
3061          }
3062  
3063          // Group JS is only enabled if site JS is enabled.
3064          $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
3065              /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
3066          );
3067  
3068          return self::getHtmlFromLoaderLinks( $links );
3069      }
3070  
3071      /**
3072       * JS stuff to put at the bottom of the "<body>"
3073       * @return string
3074       */
3075  	function getBottomScripts() {
3076          // Optimise jQuery ready event cross-browser.
3077          // This also enforces $.isReady to be true at </body> which fixes the
3078          // mw.loader bug in Firefox with using document.write between </body>
3079          // and the DOMContentReady event (bug 47457).
3080          $html = Html::inlineScript( 'window.jQuery && jQuery.ready();' );
3081  
3082          if ( !$this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
3083              $html .= $this->getScriptsForBottomQueue( false );
3084          }
3085  
3086          return $html;
3087      }
3088  
3089      /**
3090       * Get the javascript config vars to include on this page
3091       *
3092       * @return array Array of javascript config vars
3093       * @since 1.23
3094       */
3095  	public function getJsConfigVars() {
3096          return $this->mJsConfigVars;
3097      }
3098  
3099      /**
3100       * Add one or more variables to be set in mw.config in JavaScript
3101       *
3102       * @param string|array $keys Key or array of key/value pairs
3103       * @param mixed $value [optional] Value of the configuration variable
3104       */
3105  	public function addJsConfigVars( $keys, $value = null ) {
3106          if ( is_array( $keys ) ) {
3107              foreach ( $keys as $key => $value ) {
3108                  $this->mJsConfigVars[$key] = $value;
3109              }
3110              return;
3111          }
3112  
3113          $this->mJsConfigVars[$keys] = $value;
3114      }
3115  
3116      /**
3117       * Get an array containing the variables to be set in mw.config in JavaScript.
3118       *
3119       * Do not add things here which can be evaluated in ResourceLoaderStartUpModule
3120       * - in other words, page-independent/site-wide variables (without state).
3121       * You will only be adding bloat to the html page and causing page caches to
3122       * have to be purged on configuration changes.
3123       * @return array
3124       */
3125  	private function getJSVars() {
3126          global $wgContLang;
3127  
3128          $curRevisionId = 0;
3129          $articleId = 0;
3130          $canonicalSpecialPageName = false; # bug 21115
3131  
3132          $title = $this->getTitle();
3133          $ns = $title->getNamespace();
3134          $canonicalNamespace = MWNamespace::exists( $ns )
3135              ? MWNamespace::getCanonicalName( $ns )
3136              : $title->getNsText();
3137  
3138          $sk = $this->getSkin();
3139          // Get the relevant title so that AJAX features can use the correct page name
3140          // when making API requests from certain special pages (bug 34972).
3141          $relevantTitle = $sk->getRelevantTitle();
3142          $relevantUser = $sk->getRelevantUser();
3143  
3144          if ( $ns == NS_SPECIAL ) {
3145              list( $canonicalSpecialPageName, /*...*/ ) =
3146                  SpecialPageFactory::resolveAlias( $title->getDBkey() );
3147          } elseif ( $this->canUseWikiPage() ) {
3148              $wikiPage = $this->getWikiPage();
3149              $curRevisionId = $wikiPage->getLatest();
3150              $articleId = $wikiPage->getId();
3151          }
3152  
3153          $lang = $title->getPageLanguage();
3154  
3155          // Pre-process information
3156          $separatorTransTable = $lang->separatorTransformTable();
3157          $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
3158          $compactSeparatorTransTable = array(
3159              implode( "\t", array_keys( $separatorTransTable ) ),
3160              implode( "\t", $separatorTransTable ),
3161          );
3162          $digitTransTable = $lang->digitTransformTable();
3163          $digitTransTable = $digitTransTable ? $digitTransTable : array();
3164          $compactDigitTransTable = array(
3165              implode( "\t", array_keys( $digitTransTable ) ),
3166              implode( "\t", $digitTransTable ),
3167          );
3168  
3169          $user = $this->getUser();
3170  
3171          $vars = array(
3172              'wgCanonicalNamespace' => $canonicalNamespace,
3173              'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3174              'wgNamespaceNumber' => $title->getNamespace(),
3175              'wgPageName' => $title->getPrefixedDBkey(),
3176              'wgTitle' => $title->getText(),
3177              'wgCurRevisionId' => $curRevisionId,
3178              'wgRevisionId' => (int)$this->getRevisionId(),
3179              'wgArticleId' => $articleId,
3180              'wgIsArticle' => $this->isArticle(),
3181              'wgIsRedirect' => $title->isRedirect(),
3182              'wgAction' => Action::getActionName( $this->getContext() ),
3183              'wgUserName' => $user->isAnon() ? null : $user->getName(),
3184              'wgUserGroups' => $user->getEffectiveGroups(),
3185              'wgCategories' => $this->getCategories(),
3186              'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3187              'wgPageContentLanguage' => $lang->getCode(),
3188              'wgPageContentModel' => $title->getContentModel(),
3189              'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3190              'wgDigitTransformTable' => $compactDigitTransTable,
3191              'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3192              'wgMonthNames' => $lang->getMonthNamesArray(),
3193              'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3194              'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3195          );
3196  
3197          if ( $user->isLoggedIn() ) {
3198              $vars['wgUserId'] = $user->getId();
3199              $vars['wgUserEditCount'] = $user->getEditCount();
3200              $userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
3201              $vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null;
3202              // Get the revision ID of the oldest new message on the user's talk
3203              // page. This can be used for constructing new message alerts on
3204              // the client side.
3205              $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3206          }
3207  
3208          if ( $wgContLang->hasVariants() ) {
3209              $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
3210          }
3211          // Same test as SkinTemplate
3212          $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3213              && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3214  
3215          foreach ( $title->getRestrictionTypes() as $type ) {
3216              $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3217          }
3218  
3219          if ( $title->isMainPage() ) {
3220              $vars['wgIsMainPage'] = true;
3221          }
3222  
3223          if ( $this->mRedirectedFrom ) {
3224              $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3225          }
3226  
3227          if ( $relevantUser ) {
3228              $vars['wgRelevantUserName'] = $relevantUser->getName();
3229          }
3230  
3231          // Allow extensions to add their custom variables to the mw.config map.
3232          // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3233          // page-dependant but site-wide (without state).
3234          // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3235          wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
3236  
3237          // Merge in variables from addJsConfigVars last
3238          return array_merge( $vars, $this->getJsConfigVars() );
3239      }
3240  
3241      /**
3242       * To make it harder for someone to slip a user a fake
3243       * user-JavaScript or user-CSS preview, a random token
3244       * is associated with the login session. If it's not
3245       * passed back with the preview request, we won't render
3246       * the code.
3247       *
3248       * @return bool
3249       */
3250  	public function userCanPreview() {
3251          if ( $this->getRequest()->getVal( 'action' ) != 'submit'
3252              || !$this->getRequest()->wasPosted()
3253              || !$this->getUser()->matchEditToken(
3254                  $this->getRequest()->getVal( 'wpEditToken' ) )
3255          ) {
3256              return false;
3257          }
3258          if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
3259              return false;
3260          }
3261  
3262          return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
3263      }
3264  
3265      /**
3266       * @return array Array in format "link name or number => 'link html'".
3267       */
3268  	public function getHeadLinksArray() {
3269          global $wgVersion;
3270  
3271          $tags = array();
3272          $config = $this->getConfig();
3273  
3274          $canonicalUrl = $this->mCanonicalUrl;
3275  
3276          $tags['meta-generator'] = Html::element( 'meta', array(
3277              'name' => 'generator',
3278              'content' => "MediaWiki $wgVersion",
3279          ) );
3280  
3281          $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3282          if ( $p !== 'index,follow' ) {
3283              // http://www.robotstxt.org/wc/meta-user.html
3284              // Only show if it's different from the default robots policy
3285              $tags['meta-robots'] = Html::element( 'meta', array(
3286                  'name' => 'robots',
3287                  'content' => $p,
3288              ) );
3289          }
3290  
3291          foreach ( $this->mMetatags as $tag ) {
3292              if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
3293                  $a = 'http-equiv';
3294                  $tag[0] = substr( $tag[0], 5 );
3295              } else {
3296                  $a = 'name';
3297              }
3298              $tagName = "meta-{$tag[0]}";
3299              if ( isset( $tags[$tagName] ) ) {
3300                  $tagName .= $tag[1];
3301              }
3302              $tags[$tagName] = Html::element( 'meta',
3303                  array(
3304                      $a => $tag[0],
3305                      'content' => $tag[1]
3306                  )
3307              );
3308          }
3309  
3310          foreach ( $this->mLinktags as $tag ) {
3311              $tags[] = Html::element( 'link', $tag );
3312          }
3313  
3314          # Universal edit button
3315          if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3316              $user = $this->getUser();
3317              if ( $this->getTitle()->quickUserCan( 'edit', $user )
3318                  && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
3319                  // Original UniversalEditButton
3320                  $msg = $this->msg( 'edit' )->text();
3321                  $tags['universal-edit-button'] = Html::element( 'link', array(
3322                      'rel' => 'alternate',
3323                      'type' => 'application/x-wiki',
3324                      'title' => $msg,
3325                      'href' => $this->getTitle()->getEditURL(),
3326                  ) );
3327                  // Alternate edit link
3328                  $tags['alternative-edit'] = Html::element( 'link', array(
3329                      'rel' => 'edit',
3330                      'title' => $msg,
3331                      'href' => $this->getTitle()->getEditURL(),
3332                  ) );
3333              }
3334          }
3335  
3336          # Generally the order of the favicon and apple-touch-icon links
3337          # should not matter, but Konqueror (3.5.9 at least) incorrectly
3338          # uses whichever one appears later in the HTML source. Make sure
3339          # apple-touch-icon is specified first to avoid this.
3340          if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3341              $tags['apple-touch-icon'] = Html::element( 'link', array(
3342                  'rel' => 'apple-touch-icon',
3343                  'href' => $config->get( 'AppleTouchIcon' )
3344              ) );
3345          }
3346  
3347          if ( $config->get( 'Favicon' ) !== false ) {
3348              $tags['favicon'] = Html::element( 'link', array(
3349                  'rel' => 'shortcut icon',
3350                  'href' => $config->get( 'Favicon' )
3351              ) );
3352          }
3353  
3354          # OpenSearch description link
3355          $tags['opensearch'] = Html::element( 'link', array(
3356              'rel' => 'search',
3357              'type' => 'application/opensearchdescription+xml',
3358              'href' => wfScript( 'opensearch_desc' ),
3359              'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3360          ) );
3361  
3362          if ( $config->get( 'EnableAPI' ) ) {
3363              # Real Simple Discovery link, provides auto-discovery information
3364              # for the MediaWiki API (and potentially additional custom API
3365              # support such as WordPress or Twitter-compatible APIs for a
3366              # blogging extension, etc)
3367              $tags['rsd'] = Html::element( 'link', array(
3368                  'rel' => 'EditURI',
3369                  'type' => 'application/rsd+xml',
3370                  // Output a protocol-relative URL here if $wgServer is protocol-relative
3371                  // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though
3372                  'href' => wfExpandUrl( wfAppendQuery(
3373                      wfScript( 'api' ),
3374                      array( 'action' => 'rsd' ) ),
3375                      PROTO_RELATIVE
3376                  ),
3377              ) );
3378          }
3379  
3380          # Language variants
3381          if ( !$config->get( 'DisableLangConversion' ) ) {
3382              $lang = $this->getTitle()->getPageLanguage();
3383              if ( $lang->hasVariants() ) {
3384                  $variants = $lang->getVariants();
3385                  foreach ( $variants as $_v ) {
3386                      $tags["variant-$_v"] = Html::element( 'link', array(
3387                          'rel' => 'alternate',
3388                          'hreflang' => wfBCP47( $_v ),
3389                          'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
3390                      );
3391                  }
3392              }
3393              # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3394              $tags["variant-x-default"] = Html::element( 'link', array(
3395                  'rel' => 'alternate',
3396                  'hreflang' => 'x-default',
3397                  'href' => $this->getTitle()->getLocalURL() ) );
3398          }
3399  
3400          # Copyright
3401          $copyright = '';
3402          if ( $config->get( 'RightsPage' ) ) {
3403              $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3404  
3405              if ( $copy ) {
3406                  $copyright = $copy->getLocalURL();
3407              }
3408          }
3409  
3410          if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3411              $copyright = $config->get( 'RightsUrl' );
3412          }
3413  
3414          if ( $copyright ) {
3415              $tags['copyright'] = Html::element( 'link', array(
3416                  'rel' => 'copyright',
3417                  'href' => $copyright )
3418              );
3419          }
3420  
3421          # Feeds
3422          if ( $config->get( 'Feed' ) ) {
3423              foreach ( $this->getSyndicationLinks() as $format => $link ) {
3424                  # Use the page name for the title.  In principle, this could
3425                  # lead to issues with having the same name for different feeds
3426                  # corresponding to the same page, but we can't avoid that at
3427                  # this low a level.
3428  
3429                  $tags[] = $this->feedLink(
3430                      $format,
3431                      $link,
3432                      # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3433                      $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text()
3434                  );
3435              }
3436  
3437              # Recent changes feed should appear on every page (except recentchanges,
3438              # that would be redundant). Put it after the per-page feed to avoid
3439              # changing existing behavior. It's still available, probably via a
3440              # menu in your browser. Some sites might have a different feed they'd
3441              # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3442              # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3443              # If so, use it instead.
3444              $sitename = $config->get( 'Sitename' );
3445              if ( $config->get( 'OverrideSiteFeed' ) ) {
3446                  foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) {
3447                      // Note, this->feedLink escapes the url.
3448                      $tags[] = $this->feedLink(
3449                          $type,
3450                          $feedUrl,
3451                          $this->msg( "site-{$type}-feed", $sitename )->text()
3452                      );
3453                  }
3454              } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3455                  $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3456                  foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) {
3457                      $tags[] = $this->feedLink(
3458                          $format,
3459                          $rctitle->getLocalURL( array( 'feed' => $format ) ),
3460                          # For grep: 'site-rss-feed', 'site-atom-feed'
3461                          $this->msg( "site-{$format}-feed", $sitename )->text()
3462                      );
3463                  }
3464              }
3465          }
3466  
3467          # Canonical URL
3468          if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3469              if ( $canonicalUrl !== false ) {
3470                  $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3471              } else {
3472                  $reqUrl = $this->getRequest()->getRequestURL();
3473                  $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3474              }
3475          }
3476          if ( $canonicalUrl !== false ) {
3477              $tags[] = Html::element( 'link', array(
3478                  'rel' => 'canonical',
3479                  'href' => $canonicalUrl
3480              ) );
3481          }
3482  
3483          return $tags;
3484      }
3485  
3486      /**
3487       * @return string HTML tag links to be put in the header.
3488       * @deprecated since 1.24 Use OutputPage::headElement or if you have to,
3489       *   OutputPage::getHeadLinksArray directly.
3490       */
3491  	public function getHeadLinks() {
3492          wfDeprecated( __METHOD__, '1.24' );
3493          return implode( "\n", $this->getHeadLinksArray() );
3494      }
3495  
3496      /**
3497       * Generate a "<link rel/>" for a feed.
3498       *
3499       * @param string $type Feed type
3500       * @param string $url URL to the feed
3501       * @param string $text Value of the "title" attribute
3502       * @return string HTML fragment
3503       */
3504  	private function feedLink( $type, $url, $text ) {
3505          return Html::element( 'link', array(
3506              'rel' => 'alternate',
3507              'type' => "application/$type+xml",
3508              'title' => $text,
3509              'href' => $url )
3510          );
3511      }
3512  
3513      /**
3514       * Add a local or specified stylesheet, with the given media options.
3515       * Meant primarily for internal use...
3516       *
3517       * @param string $style URL to the file
3518       * @param string $media To specify a media type, 'screen', 'printable', 'handheld' or any.
3519       * @param string $condition For IE conditional comments, specifying an IE version
3520       * @param string $dir Set to 'rtl' or 'ltr' for direction-specific sheets
3521       */
3522  	public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3523          $options = array();
3524          // Even though we expect the media type to be lowercase, but here we
3525          // force it to lowercase to be safe.
3526          if ( $media ) {
3527              $options['media'] = $media;
3528          }
3529          if ( $condition ) {
3530              $options['condition'] = $condition;
3531          }
3532          if ( $dir ) {
3533              $options['dir'] = $dir;
3534          }
3535          $this->styles[$style] = $options;
3536      }
3537  
3538      /**
3539       * Adds inline CSS styles
3540       * @param mixed $style_css Inline CSS
3541       * @param string $flip Set to 'flip' to flip the CSS if needed
3542       */
3543  	public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3544          if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3545              # If wanted, and the interface is right-to-left, flip the CSS
3546              $style_css = CSSJanus::transform( $style_css, true, false );
3547          }
3548          $this->mInlineStyles .= Html::inlineStyle( $style_css ) . "\n";
3549      }
3550  
3551      /**
3552       * Build a set of "<link>" elements for the stylesheets specified in the $this->styles array.
3553       * These will be applied to various media & IE conditionals.
3554       *
3555       * @return string
3556       */
3557  	public function buildCssLinks() {
3558          global $wgContLang;
3559  
3560          $this->getSkin()->setupSkinUserCss( $this );
3561  
3562          // Add ResourceLoader styles
3563          // Split the styles into these groups
3564          $styles = array(
3565              'other' => array(),
3566              'user' => array(),
3567              'site' => array(),
3568              'private' => array(),
3569              'noscript' => array()
3570          );
3571          $links = array();
3572          $otherTags = ''; // Tags to append after the normal <link> tags
3573          $resourceLoader = $this->getResourceLoader();
3574  
3575          $moduleStyles = $this->getModuleStyles();
3576  
3577          // Per-site custom styles
3578          $moduleStyles[] = 'site';
3579          $moduleStyles[] = 'noscript';
3580          $moduleStyles[] = 'user.groups';
3581  
3582          // Per-user custom styles
3583          if ( $this->getConfig()->get( 'AllowUserCss' ) && $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
3584              // We're on a preview of a CSS subpage
3585              // Exclude this page from the user module in case it's in there (bug 26283)
3586              $link = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
3587                  array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
3588              );
3589              $otherTags .= $link['html'];
3590  
3591              // Load the previewed CSS
3592              // If needed, Janus it first. This is user-supplied CSS, so it's
3593              // assumed to be right for the content language directionality.
3594              $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
3595              if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
3596                  $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
3597              }
3598              $otherTags .= Html::inlineStyle( $previewedCSS ) . "\n";
3599          } else {
3600              // Load the user styles normally
3601              $moduleStyles[] = 'user';
3602          }
3603  
3604          // Per-user preference styles
3605          $moduleStyles[] = 'user.cssprefs';
3606  
3607          foreach ( $moduleStyles as $name ) {
3608              $module = $resourceLoader->getModule( $name );
3609              if ( !$module ) {
3610                  continue;
3611              }
3612              $group = $module->getGroup();
3613              // Modules in groups different than the ones listed on top (see $styles assignment)
3614              // will be placed in the "other" group
3615              $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
3616          }
3617  
3618          // We want site, private and user styles to override dynamically added
3619          // styles from modules, but we want dynamically added styles to override
3620          // statically added styles from other modules. So the order has to be
3621          // other, dynamic, site, private, user. Add statically added styles for
3622          // other modules
3623          $links[] = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES );
3624          // Add normal styles added through addStyle()/addInlineStyle() here
3625          $links[] = implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
3626          // Add marker tag to mark the place where the client-side loader should inject dynamic styles
3627          // We use a <meta> tag with a made-up name for this because that's valid HTML
3628          $links[] = Html::element(
3629              'meta',
3630              array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' )
3631          ) . "\n";
3632  
3633          // Add site, private and user styles
3634          // 'private' at present only contains user.options, so put that before 'user'
3635          // Any future private modules will likely have a similar user-specific character
3636          foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
3637              $links[] = $this->makeResourceLoaderLink( $styles[$group],
3638                  ResourceLoaderModule::TYPE_STYLES
3639              );
3640          }
3641  
3642          // Add stuff in $otherTags (previewed user CSS if applicable)
3643          return self::getHtmlFromLoaderLinks( $links ) . $otherTags;
3644      }
3645  
3646      /**
3647       * @return array
3648       */
3649  	public function buildCssLinksArray() {
3650          $links = array();
3651  
3652          // Add any extension CSS
3653          foreach ( $this->mExtStyles as $url ) {
3654              $this->addStyle( $url );
3655          }
3656          $this->mExtStyles = array();
3657  
3658          foreach ( $this->styles as $file => $options ) {
3659              $link = $this->styleLink( $file, $options );
3660              if ( $link ) {
3661                  $links[$file] = $link;
3662              }
3663          }
3664          return $links;
3665      }
3666  
3667      /**
3668       * Generate \<link\> tags for stylesheets
3669       *
3670       * @param string $style URL to the file
3671       * @param array $options Option, can contain 'condition', 'dir', 'media' keys
3672       * @return string HTML fragment
3673       */
3674  	protected function styleLink( $style, array $options ) {
3675          if ( isset( $options['dir'] ) ) {
3676              if ( $this->getLanguage()->getDir() != $options['dir'] ) {
3677                  return '';
3678              }
3679          }
3680  
3681          if ( isset( $options['media'] ) ) {
3682              $media = self::transformCssMedia( $options['media'] );
3683              if ( is_null( $media ) ) {
3684                  return '';
3685              }
3686          } else {
3687              $media = 'all';
3688          }
3689  
3690          if ( substr( $style, 0, 1 ) == '/' ||
3691              substr( $style, 0, 5 ) == 'http:' ||
3692              substr( $style, 0, 6 ) == 'https:' ) {
3693              $url = $style;
3694          } else {
3695              $config = $this->getConfig();
3696              $url = $config->get( 'StylePath' ) . '/' . $style . '?' . $config->get( 'StyleVersion' );
3697          }
3698  
3699          $link = Html::linkedStyle( $url, $media );
3700  
3701          if ( isset( $options['condition'] ) ) {
3702              $condition = htmlspecialchars( $options['condition'] );
3703              $link = "<!--[if $condition]>$link<![endif]-->";
3704          }
3705          return $link;
3706      }
3707  
3708      /**
3709       * Transform "media" attribute based on request parameters
3710       *
3711       * @param string $media Current value of the "media" attribute
3712       * @return string Modified value of the "media" attribute, or null to skip
3713       * this stylesheet
3714       */
3715  	public static function transformCssMedia( $media ) {
3716          global $wgRequest;
3717  
3718          // http://www.w3.org/TR/css3-mediaqueries/#syntax
3719          $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3720  
3721          // Switch in on-screen display for media testing
3722          $switches = array(
3723              'printable' => 'print',
3724              'handheld' => 'handheld',
3725          );
3726          foreach ( $switches as $switch => $targetMedia ) {
3727              if ( $wgRequest->getBool( $switch ) ) {
3728                  if ( $media == $targetMedia ) {
3729                      $media = '';
3730                  } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3731                      // This regex will not attempt to understand a comma-separated media_query_list
3732                      //
3733                      // Example supported values for $media:
3734                      // 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3735                      // Example NOT supported value for $media:
3736                      // '3d-glasses, screen, print and resolution > 90dpi'
3737                      //
3738                      // If it's a print request, we never want any kind of screen stylesheets
3739                      // If it's a handheld request (currently the only other choice with a switch),
3740                      // we don't want simple 'screen' but we might want screen queries that
3741                      // have a max-width or something, so we'll pass all others on and let the
3742                      // client do the query.
3743                      if ( $targetMedia == 'print' || $media == 'screen' ) {
3744                          return null;
3745                      }
3746                  }
3747              }
3748          }
3749  
3750          return $media;
3751      }
3752  
3753      /**
3754       * Add a wikitext-formatted message to the output.
3755       * This is equivalent to:
3756       *
3757       *    $wgOut->addWikiText( wfMessage( ... )->plain() )
3758       */
3759  	public function addWikiMsg( /*...*/ ) {
3760          $args = func_get_args();
3761          $name = array_shift( $args );
3762          $this->addWikiMsgArray( $name, $args );
3763      }
3764  
3765      /**
3766       * Add a wikitext-formatted message to the output.
3767       * Like addWikiMsg() except the parameters are taken as an array
3768       * instead of a variable argument list.
3769       *
3770       * @param string $name
3771       * @param array $args
3772       */
3773  	public function addWikiMsgArray( $name, $args ) {
3774          $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
3775      }
3776  
3777      /**
3778       * This function takes a number of message/argument specifications, wraps them in
3779       * some overall structure, and then parses the result and adds it to the output.
3780       *
3781       * In the $wrap, $1 is replaced with the first message, $2 with the second, and so
3782       * on. The subsequent arguments may either be strings, in which case they are the
3783       * message names, or arrays, in which case the first element is the message name,
3784       * and subsequent elements are the parameters to that message.
3785       *
3786       * Don't use this for messages that are not in users interface language.
3787       *
3788       * For example:
3789       *
3790       *    $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>", 'some-error' );
3791       *
3792       * Is equivalent to:
3793       *
3794       *    $wgOut->addWikiText( "<div class='error'>\n"
3795       *        . wfMessage( 'some-error' )->plain() . "\n</div>" );
3796       *
3797       * The newline after opening div is needed in some wikitext. See bug 19226.
3798       *
3799       * @param string $wrap
3800       */
3801  	public function wrapWikiMsg( $wrap /*, ...*/ ) {
3802          $msgSpecs = func_get_args();
3803          array_shift( $msgSpecs );
3804          $msgSpecs = array_values( $msgSpecs );
3805          $s = $wrap;
3806          foreach ( $msgSpecs as $n => $spec ) {
3807              if ( is_array( $spec ) ) {
3808                  $args = $spec;
3809                  $name = array_shift( $args );
3810                  if ( isset( $args['options'] ) ) {
3811                      unset( $args['options'] );
3812                      wfDeprecated(
3813                          'Adding "options" to ' . __METHOD__ . ' is no longer supported',
3814                          '1.20'
3815                      );
3816                  }
3817              } else {
3818                  $args = array();
3819                  $name = $spec;
3820              }
3821              $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
3822          }
3823          $this->addWikiText( $s );
3824      }
3825  
3826      /**
3827       * Include jQuery core. Use this to avoid loading it multiple times
3828       * before we get a usable script loader.
3829       *
3830       * @param array $modules List of jQuery modules which should be loaded
3831       * @return array The list of modules which were not loaded.
3832       * @since 1.16
3833       * @deprecated since 1.17
3834       */
3835  	public function includeJQuery( array $modules = array() ) {
3836          return array();
3837      }
3838  
3839      /**
3840       * Enables/disables TOC, doesn't override __NOTOC__
3841       * @param bool $flag
3842       * @since 1.22
3843       */
3844  	public function enableTOC( $flag = true ) {
3845          $this->mEnableTOC = $flag;
3846      }
3847  
3848      /**
3849       * @return bool
3850       * @since 1.22
3851       */
3852  	public function isTOCEnabled() {
3853          return $this->mEnableTOC;
3854      }
3855  
3856      /**
3857       * Enables/disables section edit links, doesn't override __NOEDITSECTION__
3858       * @param bool $flag
3859       * @since 1.23
3860       */
3861  	public function enableSectionEditLinks( $flag = true ) {
3862          $this->mEnableSectionEditLinks = $flag;
3863      }
3864  
3865      /**
3866       * @return bool
3867       * @since 1.23
3868       */
3869  	public function sectionEditLinksEnabled() {
3870          return $this->mEnableSectionEditLinks;
3871      }
3872  }
". ', [['includes','Html.php',597]], 7], 'sendcachecontrol': ['sendcachecontrol', 'Send cache control HTTP headers ', [['includes','OutputPage.php',2023]], 7], 'setcanonicalurl': ['setcanonicalurl', 'Set the URL to be used for the . This should be used in preference to addLink(), to avoid duplicate link tags. ', [['includes','OutputPage.php',389]], 2], 'htmlheader': ['htmlheader', 'Constructs the opening html-tag with necessary doctypes depending on global variables. ', [['includes','Html.php',875]], 4], 'addvaryheader': ['addvaryheader', 'Add an HTTP header that will influence on the cache ', [['includes','OutputPage.php',1878]], 10], 'includejquery': ['includejquery', 'Include jQuery core. Use this to avoid loading it multiple times before we get a usable script loader. ', [['includes','OutputPage.php',3826]], 0], 'stylelink': ['stylelink', 'Generate \ tags for stylesheets ', [['includes','OutputPage.php',3667]], 1], 'geteditcount': ['geteditcount', 'Get the user\'s edit count. ', [['includes','User.php',2973]], 12], 'capitalizeallnouns': ['capitalizeallnouns', '', [['languages','Language.php',3078]], 2], 'setpagetitleactiontext': ['setpagetitleactiontext', 'Set the new value of the "action text", this will be added to the "HTML title", separated from it with " - ". ', [['includes','OutputPage.php',872]], 0], 'setarticlerelated': ['setarticlerelated', 'Set whether this page is related an article on the wiki Setting false will cause the change of "article flag" toggle to false ', [['includes','OutputPage.php',1180]], 7], 'stripalltags': ['stripalltags', 'Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text. ', [['includes','Sanitizer.php',1740]], 4], 'setlanguagelinks': ['setlanguagelinks', 'Reset the language links and add new language links ', [['includes','OutputPage.php',1212],['includes/parser','ParserOutput.php',230]], 0], 'gethtml': ['gethtml', 'Returns HTML for reporting the HTTP error. This will be a minimal but complete HTML document. ', [['includes/exception','HttpError.php',68],['includes/revisiondelete','RevDelLogItem.php',79],['includes','CategoryViewer.php',101],['includes/revisiondelete','RevDelRevisionItem.php',150],['includes/content','MessageContent.php',62],['includes/skins','SkinTemplate.php',1515],['includes/content','CssContent.php',68],['includes','Xml.php',971],['includes','OutputPage.php',1441],['includes/content','TextContent.php',237],['includes','RevisionList.php',382],['includes/htmlform','HTMLForm.php',779],['includes/exception','MWException.php',126],['includes/exception','FatalError.php',33],['includes/revisiondelete','RevDelFileItem.php',183],['includes/content','WikitextContent.php',358],['includes','Status.php',303],['includes/db','DatabaseError.php',69],['includes/db','DatabaseError.php',156],['includes/content','JavaScriptContent.php',67]], 22], 'setcategorylinks': ['setcategorylinks', 'Reset the category links (but not the category list) and add $categories ', [['includes','OutputPage.php',1303],['includes/parser','ParserOutput.php',234]], 1], 'seteditsection': ['seteditsection', '', [['includes/parser','ParserOptions.php',416]], 11], 'getconfig': ['getconfig', 'Get the Config object ', [['includes/context','ContextSource.php',62],['includes/specialpage','SpecialPage.php',547],['includes/context','DerivativeContext.php',90],['maintenance','Maintenance.php',471],['includes/resourceloader','ResourceLoaderModule.php',141],['includes/resourceloader','ResourceLoader.php',252],['includes/context','RequestContext.php',85],['includes','SiteConfiguration.php',505],['includes/context','IContextSource.php',94]], 309], 'linkedstyle': ['linkedstyle', 'Output a "" linking to the given URL for the given media type (if any). ', [['includes','Html.php',632]], 3], 'linkknown': ['linkknown', 'Identical to link(), except $options defaults to \'known\'. ', [['includes','Linker.php',260]], 196], 'wfsetvar': ['wfsetvar', 'Sets dest to source and returns the original value of dest If source is NULL, it just returns the value, it doesn\'t set the variable If force is true, it will set the value even if source is NULL ', [['includes','GlobalFunctions.php',2087]], 70], 'showerrorpage': ['showerrorpage', 'Output a standard error page ', [['includes','OutputPage.php',2251]], 5], 'settarget': ['settarget', 'Set the target for this block, and update $this->type accordingly ', [['includes','Block.php',1325],['includes/logging','LogEntry.php',428],['includes','OutputPage.php',614]], 22], 'empty': ['empty', '', [['resources/lib/es5-shim','es5-shim.js',189],['resources/src','polyfill-object-create.js',24]], 426], 'hasvariants': ['hasvariants', 'Check if this is a language with variants ', [['languages','Language.php',3956]], 11], 'isdisabled': ['isdisabled', 'Return whether the output will be completely disabled ', [['includes','OutputPage.php',1055],['includes/api','ApiFormatBase.php',118],['includes','Message.php',830]], 89], 'getmodules': ['getmodules', 'Get a list of modules to include in the page. ', [['includes/gallery','TraditionalImageGallery.php',304],['includes','OutputPage.php',510],['includes/api','ApiQuery.php',184],['includes/resourceloader','DerivativeResourceLoaderContext.php',51],['includes/resourceloader','ResourceLoaderContext.php',138],['includes/parser','ParserOutput.php',177],['includes/api','ApiMain.php',1402],['includes/gallery','PackedImageGallery.php',95]], 15], 'isloggedin': ['isloggedin', 'Get whether the user is logged in ', [['includes','User.php',3069]], 62], 'getarticlebodyonly': ['getarticlebodyonly', 'Return whether the output will contain only the body of the article ', [['includes','OutputPage.php',688]], 1], 'setinterfacemessage': ['setinterfacemessage', '', [['includes/parser','ParserOptions.php',432]], 5], 'showpermissionserrorpage': ['showpermissionserrorpage', 'Output a standard permission error page ', [['includes','OutputPage.php',2284]], 1], 'setfeedappendquery': ['setfeedappendquery', 'Add default feeds to the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version ', [['includes','OutputPage.php',1098]], 3], 'makeconfigsetscript': ['makeconfigsetscript', 'Returns JS code which will set the MediaWiki configuration array to the given value. ', [['includes/resourceloader','ResourceLoader.php',1254]], 3], 'isredirect': ['isredirect', '', [['includes/specials','SpecialRandompage.php',52],['includes','Title.php',3162],['includes','HttpFunctions.php',520],['includes/page','WikiPage.php',490],['includes/page','WikiFilePage.php',105],['includes/content','Content.php',353],['includes/content','AbstractContent.php',307]], 35], 'addparseroutput': ['addparseroutput', 'Add everything from a ParserOutput object. ', [['includes','OutputPage.php',1729]], 5], 'gettext': ['gettext', '', [], 362], 'is_array': ['is_array', '', [], 594], 'count': ['count', '', [], 1475], 'trim': ['trim', '', [], 340], 'in_array': ['in_array', '', [], 760], 'call_user_func_array': ['call_user_func_array', '', [], 93], 'session_id': ['session_id', '', [], 22], 'time': ['time', '', [], 171], 'array_shift': ['array_shift', '', [], 115], 'implode': ['implode', '', [], 454], 'sort': ['sort', '', [], 63], 'array_key_exists': ['array_key_exists', '', [], 141], 'substr': ['substr', '', [], 891], 'ini_set': ['ini_set', '', [], 27], 'str_replace': ['str_replace', '', [], 391], 'is_null': ['is_null', '', [], 800], 'getdir': ['getdir', '', [], 38], 'func_get_args': ['func_get_args', '', [], 96], 'array_keys': ['array_keys', '', [], 314], 'array_unique': ['array_unique', '', [], 102], 'strcasecmp': ['strcasecmp', '', [], 15], 'array_merge': ['array_merge', '', [], 382], 'trigger_error': ['trigger_error', '', [], 39], 'is_string': ['is_string', '', [], 167], 'strtotime': ['strtotime', '', [], 18], 'gmdate': ['gmdate', '', [], 16], 'array_values': ['array_values', '', [], 86], 'intval': ['intval', '', [], 549], 'call_user_func': ['call_user_func', '', [], 93], 'ucfirst': ['ucfirst', '', [], 61], 'max': ['max', '', [], 197], 'array_push': ['array_push', '', [], 72], 'htmlspecialchars': ['htmlspecialchars', '', [], 384], 'ob_start': ['ob_start', '', [], 24], 'strpos': ['strpos', '', [], 315], 'preg_match': ['preg_match', '', [], 561], 'session_name': ['session_name', '', [], 5], 'ob_end_flush': ['ob_end_flush', '', [], 8], 'reset': ['reset', '', [], 88], 'preg_replace': ['preg_replace', '', [], 301], 'link': ['link', '', [], 203], 'is_object': ['is_object', '', [], 127], 'min': ['min', '', [], 120], 'header': ['header', '', [], 174]}; CLASS_DATA={ 'parseroptions': ['parseroptions', '', [['includes/parser','ParserOptions.php',32]], 37], 'linkcache': ['linkcache', 'Cache for article titles (prefixed DB keys) and ids linked from one source ', [['includes/cache','LinkCache.php',24]], 23], 'cssjanus': ['cssjanus', 'This is a PHP port of CSSJanus, a utility that transforms CSS style sheets written for LTR to RTL. ', [['includes/libs','CSSJanus.php',23]], 5], 'readonlyerror': ['readonlyerror', 'Show an error when the wiki is locked/read-only and the user tries to do something that requires write access. ', [['includes/exception','ReadOnlyError.php',21]], 11], 'xml': ['xml', 'Module of static functions for generating XML ', [['includes','Xml.php',23]], 911], 'user': ['user', 'The User object encapsulates all of the user-specific settings (user_id, name, rights, password, email address, options, last login time). Client classes use the getXXX() functions to access these fields. These functions do all the work of determining whether the user is logged in, whether the requested option can be satisfied from cookies or whether a database query is needed. Most of the settings needed for rendering normal pages are set in the cookie to minimize use of the database. ', [['includes','User.php',29]], 328], 'specialpagefactory': ['specialpagefactory', 'Factory for handling the special page list and generating SpecialPage objects. ', [['includes/specialpage','SpecialPageFactory.php',25]], 27], 'resourceloadercontext': ['resourceloadercontext', 'Object passed around to modules which contains information about the state of a specific loader request ', [['includes/resourceloader','ResourceLoaderContext.php',25]], 5], 'mwnamespace': ['mwnamespace', 'This is a utility class with only static functions for dealing with namespaces that encodes all the "magic" behaviors of them based on index. The textual names of the namespaces are handled by Language.php. ', [['includes','MWNamespace.php',23]], 132], 'resourceloader': ['resourceloader', 'Dynamic JavaScript and CSS resource loading system. ', [['includes/resourceloader','ResourceLoader.php',25]], 48], 'outputpage': ['outputpage', 'This class should be covered by a general architecture document which does not exist as of January 2011. This is one of the Core classes and should be read at least once by any new developers. ', [['includes','OutputPage.php',23]], 23], 'mwexception': ['mwexception', 'MediaWiki exception ', [['includes/exception','MWException.php',21]], 606], 'httpstatus': ['httpstatus', '', [['includes/libs','HttpStatus.php',26]], 7], 'fauxrequest': ['fauxrequest', 'WebRequest clone which takes values from a provided array. ', [['includes','WebRequest.php',1250]], 12], 'linker': ['linker', 'Some internal bits split of from Skin.php. These functions are used for primarily page content: links, embedded images, table of contents. Links are also used in the skin. ', [['includes','Linker.php',23]], 551], 'permissionserror': ['permissionserror', 'Show an error when a user tries to do something they do not have the necessary permissions for. ', [['includes/exception','PermissionsError.php',21]], 41], 'title': ['title', 'Represents a title within MediaWiki. Optionally may contain an interwiki designation or namespace. ', [['includes','Title.php',25]], 710], 'mwdebug': ['mwdebug', 'New debugger system that outputs a toolbar on page view. ', [['includes/debug','MWDebug.php',23]], 14], 'throttlederror': ['throttlederror', 'Show an error when the user hits a rate limit. ', [['includes/exception','ThrottledError.php',21]], 6], 'userblockederror': ['userblockederror', 'Show an error when the user tries to do something whilst blocked. ', [['includes/exception','UserBlockedError.php',21]], 9], 'parser': ['parser', 'PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for making links), and provides a one-way transformation of that wiki markup it into (X)HTML output / markup (which in turn the browser understands, and can display). ', [['includes/parser','Parser.php',28]], 46], 'specialpage': ['specialpage', 'Parent class for all special pages. ', [['includes/specialpage','SpecialPage.php',24]], 241], 'linkbatch': ['linkbatch', 'Class representing a list of titles The execute() method checks them all for existence and adds them to a LinkCache object ', [['includes/cache','LinkBatch.php',24]], 50], 'html': ['html', 'This class is a collection of static functions that serve two purposes: ', [['includes','Html.php',26]], 764], 'article': ['article', 'Class for viewing MediaWiki article and history. ', [['includes/page','Article.php',23],['docs','hooks.txt',210]], 52], 'sanitizer': ['sanitizer', 'HTML sanitizer for MediaWiki ', [['includes','Sanitizer.php',27]], 147]}; CONST_DATA={ 'PROTO_CANONICAL': ['PROTO_CANONICAL', '', [['includes','Defines.php',268]], 12], 'PROTO_CURRENT': ['PROTO_CURRENT', '', [['includes','Defines.php',267]], 33], 'NS_SPECIAL': ['NS_SPECIAL', '', [['includes','Defines.php',67]], 327], 'TS_RFC2822': ['TS_RFC2822', '', [['includes','GlobalFunctions.php',2399]], 14], 'NS_CATEGORY': ['NS_CATEGORY', '', [['includes','Defines.php',92]], 402], 'TS_ISO_8601': ['TS_ISO_8601', '', [['includes','GlobalFunctions.php',2406]], 65], 'TS_MW': ['TS_MW', '', [['includes','GlobalFunctions.php',2389]], 126], 'TS_UNIX': ['TS_UNIX', '', [['includes','GlobalFunctions.php',2384]], 74], 'PROTO_RELATIVE': ['PROTO_RELATIVE', '', [['includes','Defines.php',266]], 18], 'TS_ISO_8601_BASIC': ['TS_ISO_8601_BASIC', '', [['includes','GlobalFunctions.php',2430]], 6], 'DB_SLAVE': ['DB_SLAVE', '', [['includes','Defines.php',55]], 283]};


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