[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/page/ -> Article.php (source)

   1  <?php
   2  /**
   3   * User interface for page actions.
   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   * Class for viewing MediaWiki article and history.
  25   *
  26   * This maintains WikiPage functions for backwards compatibility.
  27   *
  28   * @todo Move and rewrite code to an Action class
  29   *
  30   * See design.txt for an overview.
  31   * Note: edit user interface and cache support functions have been
  32   * moved to separate EditPage and HTMLFileCache classes.
  33   *
  34   * @internal documentation reviewed 15 Mar 2010
  35   */
  36  class Article implements Page {
  37      /** @var IContextSource The context this Article is executed in */
  38      protected $mContext;
  39  
  40      /** @var WikiPage The WikiPage object of this instance */
  41      protected $mPage;
  42  
  43      /** @var ParserOptions ParserOptions object for $wgUser articles */
  44      public $mParserOptions;
  45  
  46      /**
  47       * @var string Text of the revision we are working on
  48       * @todo BC cruft
  49       */
  50      public $mContent;
  51  
  52      /**
  53       * @var Content Content of the revision we are working on
  54       * @since 1.21
  55       */
  56      public $mContentObject;
  57  
  58      /** @var bool Is the content ($mContent) already loaded? */
  59      public $mContentLoaded = false;
  60  
  61      /** @var int|null The oldid of the article that is to be shown, 0 for the current revision */
  62      public $mOldId;
  63  
  64      /** @var Title Title from which we were redirected here */
  65      public $mRedirectedFrom = null;
  66  
  67      /** @var string|bool URL to redirect to or false if none */
  68      public $mRedirectUrl = false;
  69  
  70      /** @var int Revision ID of revision we are working on */
  71      public $mRevIdFetched = 0;
  72  
  73      /** @var Revision Revision we are working on */
  74      public $mRevision = null;
  75  
  76      /** @var ParserOutput */
  77      public $mParserOutput;
  78  
  79      /**
  80       * Constructor and clear the article
  81       * @param Title $title Reference to a Title object.
  82       * @param int $oldId Revision ID, null to fetch from request, zero for current
  83       */
  84  	public function __construct( Title $title, $oldId = null ) {
  85          $this->mOldId = $oldId;
  86          $this->mPage = $this->newPage( $title );
  87      }
  88  
  89      /**
  90       * @param Title $title
  91       * @return WikiPage
  92       */
  93  	protected function newPage( Title $title ) {
  94          return new WikiPage( $title );
  95      }
  96  
  97      /**
  98       * Constructor from a page id
  99       * @param int $id Article ID to load
 100       * @return Article|null
 101       */
 102  	public static function newFromID( $id ) {
 103          $t = Title::newFromID( $id );
 104          # @todo FIXME: Doesn't inherit right
 105          return $t == null ? null : new self( $t );
 106          # return $t == null ? null : new static( $t ); // PHP 5.3
 107      }
 108  
 109      /**
 110       * Create an Article object of the appropriate class for the given page.
 111       *
 112       * @param Title $title
 113       * @param IContextSource $context
 114       * @return Article
 115       */
 116  	public static function newFromTitle( $title, IContextSource $context ) {
 117          if ( NS_MEDIA == $title->getNamespace() ) {
 118              // FIXME: where should this go?
 119              $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
 120          }
 121  
 122          $page = null;
 123          wfRunHooks( 'ArticleFromTitle', array( &$title, &$page, $context ) );
 124          if ( !$page ) {
 125              switch ( $title->getNamespace() ) {
 126                  case NS_FILE:
 127                      $page = new ImagePage( $title );
 128                      break;
 129                  case NS_CATEGORY:
 130                      $page = new CategoryPage( $title );
 131                      break;
 132                  default:
 133                      $page = new Article( $title );
 134              }
 135          }
 136          $page->setContext( $context );
 137  
 138          return $page;
 139      }
 140  
 141      /**
 142       * Create an Article object of the appropriate class for the given page.
 143       *
 144       * @param WikiPage $page
 145       * @param IContextSource $context
 146       * @return Article
 147       */
 148  	public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
 149          $article = self::newFromTitle( $page->getTitle(), $context );
 150          $article->mPage = $page; // override to keep process cached vars
 151          return $article;
 152      }
 153  
 154      /**
 155       * Tell the page view functions that this view was redirected
 156       * from another page on the wiki.
 157       * @param Title $from
 158       */
 159  	public function setRedirectedFrom( Title $from ) {
 160          $this->mRedirectedFrom = $from;
 161      }
 162  
 163      /**
 164       * Get the title object of the article
 165       *
 166       * @return Title Title object of this page
 167       */
 168  	public function getTitle() {
 169          return $this->mPage->getTitle();
 170      }
 171  
 172      /**
 173       * Get the WikiPage object of this instance
 174       *
 175       * @since 1.19
 176       * @return WikiPage
 177       */
 178  	public function getPage() {
 179          return $this->mPage;
 180      }
 181  
 182      /**
 183       * Clear the object
 184       */
 185  	public function clear() {
 186          $this->mContentLoaded = false;
 187  
 188          $this->mRedirectedFrom = null; # Title object if set
 189          $this->mRevIdFetched = 0;
 190          $this->mRedirectUrl = false;
 191  
 192          $this->mPage->clear();
 193      }
 194  
 195      /**
 196       * Note that getContent/loadContent do not follow redirects anymore.
 197       * If you need to fetch redirectable content easily, try
 198       * the shortcut in WikiPage::getRedirectTarget()
 199       *
 200       * This function has side effects! Do not use this function if you
 201       * only want the real revision text if any.
 202       *
 203       * @deprecated since 1.21; use WikiPage::getContent() instead
 204       *
 205       * @return string Return the text of this revision
 206       */
 207  	public function getContent() {
 208          ContentHandler::deprecated( __METHOD__, '1.21' );
 209          $content = $this->getContentObject();
 210          return ContentHandler::getContentText( $content );
 211      }
 212  
 213      /**
 214       * Returns a Content object representing the pages effective display content,
 215       * not necessarily the revision's content!
 216       *
 217       * Note that getContent/loadContent do not follow redirects anymore.
 218       * If you need to fetch redirectable content easily, try
 219       * the shortcut in WikiPage::getRedirectTarget()
 220       *
 221       * This function has side effects! Do not use this function if you
 222       * only want the real revision text if any.
 223       *
 224       * @return Content Return the content of this revision
 225       *
 226       * @since 1.21
 227       */
 228  	protected function getContentObject() {
 229          wfProfileIn( __METHOD__ );
 230  
 231          if ( $this->mPage->getID() === 0 ) {
 232              # If this is a MediaWiki:x message, then load the messages
 233              # and return the message value for x.
 234              if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
 235                  $text = $this->getTitle()->getDefaultMessageText();
 236                  if ( $text === false ) {
 237                      $text = '';
 238                  }
 239  
 240                  $content = ContentHandler::makeContent( $text, $this->getTitle() );
 241              } else {
 242                  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
 243                  $content = new MessageContent( $message, null, 'parsemag' );
 244              }
 245          } else {
 246              $this->fetchContentObject();
 247              $content = $this->mContentObject;
 248          }
 249  
 250          wfProfileOut( __METHOD__ );
 251          return $content;
 252      }
 253  
 254      /**
 255       * @return int The oldid of the article that is to be shown, 0 for the current revision
 256       */
 257  	public function getOldID() {
 258          if ( is_null( $this->mOldId ) ) {
 259              $this->mOldId = $this->getOldIDFromRequest();
 260          }
 261  
 262          return $this->mOldId;
 263      }
 264  
 265      /**
 266       * Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect
 267       *
 268       * @return int The old id for the request
 269       */
 270  	public function getOldIDFromRequest() {
 271          $this->mRedirectUrl = false;
 272  
 273          $request = $this->getContext()->getRequest();
 274          $oldid = $request->getIntOrNull( 'oldid' );
 275  
 276          if ( $oldid === null ) {
 277              return 0;
 278          }
 279  
 280          if ( $oldid !== 0 ) {
 281              # Load the given revision and check whether the page is another one.
 282              # In that case, update this instance to reflect the change.
 283              if ( $oldid === $this->mPage->getLatest() ) {
 284                  $this->mRevision = $this->mPage->getRevision();
 285              } else {
 286                  $this->mRevision = Revision::newFromId( $oldid );
 287                  if ( $this->mRevision !== null ) {
 288                      // Revision title doesn't match the page title given?
 289                      if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
 290                          $function = array( get_class( $this->mPage ), 'newFromID' );
 291                          $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
 292                      }
 293                  }
 294              }
 295          }
 296  
 297          if ( $request->getVal( 'direction' ) == 'next' ) {
 298              $nextid = $this->getTitle()->getNextRevisionID( $oldid );
 299              if ( $nextid ) {
 300                  $oldid = $nextid;
 301                  $this->mRevision = null;
 302              } else {
 303                  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
 304              }
 305          } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
 306              $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
 307              if ( $previd ) {
 308                  $oldid = $previd;
 309                  $this->mRevision = null;
 310              }
 311          }
 312  
 313          return $oldid;
 314      }
 315  
 316      /**
 317       * Load the revision (including text) into this object
 318       *
 319       * @deprecated since 1.19; use fetchContent()
 320       */
 321  	function loadContent() {
 322          wfDeprecated( __METHOD__, '1.19' );
 323          $this->fetchContent();
 324      }
 325  
 326      /**
 327       * Get text of an article from database
 328       * Does *NOT* follow redirects.
 329       *
 330       * @protected
 331       * @note This is really internal functionality that should really NOT be
 332       * used by other functions. For accessing article content, use the WikiPage
 333       * class, especially WikiBase::getContent(). However, a lot of legacy code
 334       * uses this method to retrieve page text from the database, so the function
 335       * has to remain public for now.
 336       *
 337       * @return string|bool String containing article contents, or false if null
 338       * @deprecated since 1.21, use WikiPage::getContent() instead
 339       */
 340  	function fetchContent() { #BC cruft!
 341          ContentHandler::deprecated( __METHOD__, '1.21' );
 342  
 343          if ( $this->mContentLoaded && $this->mContent ) {
 344              return $this->mContent;
 345          }
 346  
 347          wfProfileIn( __METHOD__ );
 348  
 349          $content = $this->fetchContentObject();
 350  
 351          if ( !$content ) {
 352              wfProfileOut( __METHOD__ );
 353              return false;
 354          }
 355  
 356          // @todo Get rid of mContent everywhere!
 357          $this->mContent = ContentHandler::getContentText( $content );
 358          ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
 359  
 360          wfProfileOut( __METHOD__ );
 361  
 362          return $this->mContent;
 363      }
 364  
 365      /**
 366       * Get text content object
 367       * Does *NOT* follow redirects.
 368       * @todo When is this null?
 369       *
 370       * @note Code that wants to retrieve page content from the database should
 371       * use WikiPage::getContent().
 372       *
 373       * @return Content|null|bool
 374       *
 375       * @since 1.21
 376       */
 377  	protected function fetchContentObject() {
 378          if ( $this->mContentLoaded ) {
 379              return $this->mContentObject;
 380          }
 381  
 382          wfProfileIn( __METHOD__ );
 383  
 384          $this->mContentLoaded = true;
 385          $this->mContent = null;
 386  
 387          $oldid = $this->getOldID();
 388  
 389          # Pre-fill content with error message so that if something
 390          # fails we'll have something telling us what we intended.
 391          //XXX: this isn't page content but a UI message. horrible.
 392          $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() );
 393  
 394          if ( $oldid ) {
 395              # $this->mRevision might already be fetched by getOldIDFromRequest()
 396              if ( !$this->mRevision ) {
 397                  $this->mRevision = Revision::newFromId( $oldid );
 398                  if ( !$this->mRevision ) {
 399                      wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
 400                      wfProfileOut( __METHOD__ );
 401                      return false;
 402                  }
 403              }
 404          } else {
 405              if ( !$this->mPage->getLatest() ) {
 406                  wfDebug( __METHOD__ . " failed to find page data for title " .
 407                      $this->getTitle()->getPrefixedText() . "\n" );
 408                  wfProfileOut( __METHOD__ );
 409                  return false;
 410              }
 411  
 412              $this->mRevision = $this->mPage->getRevision();
 413  
 414              if ( !$this->mRevision ) {
 415                  wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " .
 416                      $this->mPage->getLatest() . "\n" );
 417                  wfProfileOut( __METHOD__ );
 418                  return false;
 419              }
 420          }
 421  
 422          // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
 423          // We should instead work with the Revision object when we need it...
 424          // Loads if user is allowed
 425          $this->mContentObject = $this->mRevision->getContent(
 426              Revision::FOR_THIS_USER,
 427              $this->getContext()->getUser()
 428          );
 429          $this->mRevIdFetched = $this->mRevision->getId();
 430  
 431          wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
 432  
 433          wfProfileOut( __METHOD__ );
 434  
 435          return $this->mContentObject;
 436      }
 437  
 438      /**
 439       * Returns true if the currently-referenced revision is the current edit
 440       * to this page (and it exists).
 441       * @return bool
 442       */
 443  	public function isCurrent() {
 444          # If no oldid, this is the current version.
 445          if ( $this->getOldID() == 0 ) {
 446              return true;
 447          }
 448  
 449          return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
 450      }
 451  
 452      /**
 453       * Get the fetched Revision object depending on request parameters or null
 454       * on failure.
 455       *
 456       * @since 1.19
 457       * @return Revision|null
 458       */
 459  	public function getRevisionFetched() {
 460          $this->fetchContentObject();
 461  
 462          return $this->mRevision;
 463      }
 464  
 465      /**
 466       * Use this to fetch the rev ID used on page views
 467       *
 468       * @return int Revision ID of last article revision
 469       */
 470  	public function getRevIdFetched() {
 471          if ( $this->mRevIdFetched ) {
 472              return $this->mRevIdFetched;
 473          } else {
 474              return $this->mPage->getLatest();
 475          }
 476      }
 477  
 478      /**
 479       * This is the default action of the index.php entry point: just view the
 480       * page of the given title.
 481       */
 482  	public function view() {
 483          global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects;
 484  
 485          wfProfileIn( __METHOD__ );
 486  
 487          # Get variables from query string
 488          # As side effect this will load the revision and update the title
 489          # in a revision ID is passed in the request, so this should remain
 490          # the first call of this method even if $oldid is used way below.
 491          $oldid = $this->getOldID();
 492  
 493          $user = $this->getContext()->getUser();
 494          # Another whitelist check in case getOldID() is altering the title
 495          $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
 496          if ( count( $permErrors ) ) {
 497              wfDebug( __METHOD__ . ": denied on secondary read check\n" );
 498              wfProfileOut( __METHOD__ );
 499              throw new PermissionsError( 'read', $permErrors );
 500          }
 501  
 502          $outputPage = $this->getContext()->getOutput();
 503          # getOldID() may as well want us to redirect somewhere else
 504          if ( $this->mRedirectUrl ) {
 505              $outputPage->redirect( $this->mRedirectUrl );
 506              wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
 507              wfProfileOut( __METHOD__ );
 508  
 509              return;
 510          }
 511  
 512          # If we got diff in the query, we want to see a diff page instead of the article.
 513          if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
 514              wfDebug( __METHOD__ . ": showing diff page\n" );
 515              $this->showDiffPage();
 516              wfProfileOut( __METHOD__ );
 517  
 518              return;
 519          }
 520  
 521          # Set page title (may be overridden by DISPLAYTITLE)
 522          $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
 523  
 524          $outputPage->setArticleFlag( true );
 525          # Allow frames by default
 526          $outputPage->allowClickjacking();
 527  
 528          $parserCache = ParserCache::singleton();
 529  
 530          $parserOptions = $this->getParserOptions();
 531          # Render printable version, use printable version cache
 532          if ( $outputPage->isPrintable() ) {
 533              $parserOptions->setIsPrintable( true );
 534              $parserOptions->setEditSection( false );
 535          } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
 536              $parserOptions->setEditSection( false );
 537          }
 538  
 539          # Try client and file cache
 540          if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
 541              if ( $wgUseETag ) {
 542                  $outputPage->setETag( $parserCache->getETag( $this, $parserOptions ) );
 543              }
 544  
 545              # Use the greatest of the page's timestamp or the timestamp of any
 546              # redirect in the chain (bug 67849)
 547              $timestamp = $this->mPage->getTouched();
 548              if ( isset( $this->mRedirectedFrom ) ) {
 549                  $timestamp = max( $timestamp, $this->mRedirectedFrom->getTouched() );
 550  
 551                  # If there can be more than one redirect in the chain, we have
 552                  # to go through the whole chain too in case an intermediate
 553                  # redirect was changed.
 554                  if ( $wgMaxRedirects > 1 ) {
 555                      $titles = Revision::newFromTitle( $this->mRedirectedFrom )
 556                          ->getContent( Revision::FOR_THIS_USER, $user )
 557                          ->getRedirectChain();
 558                      $thisTitle = $this->getTitle();
 559                      foreach ( $titles as $title ) {
 560                          if ( Title::compare( $title, $thisTitle ) === 0 ) {
 561                              break;
 562                          }
 563                          $timestamp = max( $timestamp, $title->getTouched() );
 564                      }
 565                  }
 566              }
 567  
 568              # Is it client cached?
 569              if ( $outputPage->checkLastModified( $timestamp ) ) {
 570                  wfDebug( __METHOD__ . ": done 304\n" );
 571                  wfProfileOut( __METHOD__ );
 572  
 573                  return;
 574              # Try file cache
 575              } elseif ( $wgUseFileCache && $this->tryFileCache() ) {
 576                  wfDebug( __METHOD__ . ": done file cache\n" );
 577                  # tell wgOut that output is taken care of
 578                  $outputPage->disable();
 579                  $this->mPage->doViewUpdates( $user, $oldid );
 580                  wfProfileOut( __METHOD__ );
 581  
 582                  return;
 583              }
 584          }
 585  
 586          # Should the parser cache be used?
 587          $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
 588          wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
 589          if ( $user->getStubThreshold() ) {
 590              wfIncrStats( 'pcache_miss_stub' );
 591          }
 592  
 593          $this->showRedirectedFromHeader();
 594          $this->showNamespaceHeader();
 595  
 596          # Iterate through the possible ways of constructing the output text.
 597          # Keep going until $outputDone is set, or we run out of things to do.
 598          $pass = 0;
 599          $outputDone = false;
 600          $this->mParserOutput = false;
 601  
 602          while ( !$outputDone && ++$pass ) {
 603              switch ( $pass ) {
 604                  case 1:
 605                      wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
 606                      break;
 607                  case 2:
 608                      # Early abort if the page doesn't exist
 609                      if ( !$this->mPage->exists() ) {
 610                          wfDebug( __METHOD__ . ": showing missing article\n" );
 611                          $this->showMissingArticle();
 612                          $this->mPage->doViewUpdates( $user );
 613                          wfProfileOut( __METHOD__ );
 614                          return;
 615                      }
 616  
 617                      # Try the parser cache
 618                      if ( $useParserCache ) {
 619                          $this->mParserOutput = $parserCache->get( $this, $parserOptions );
 620  
 621                          if ( $this->mParserOutput !== false ) {
 622                              if ( $oldid ) {
 623                                  wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
 624                                  $this->setOldSubtitle( $oldid );
 625                              } else {
 626                                  wfDebug( __METHOD__ . ": showing parser cache contents\n" );
 627                              }
 628                              $outputPage->addParserOutput( $this->mParserOutput );
 629                              # Ensure that UI elements requiring revision ID have
 630                              # the correct version information.
 631                              $outputPage->setRevisionId( $this->mPage->getLatest() );
 632                              # Preload timestamp to avoid a DB hit
 633                              $cachedTimestamp = $this->mParserOutput->getTimestamp();
 634                              if ( $cachedTimestamp !== null ) {
 635                                  $outputPage->setRevisionTimestamp( $cachedTimestamp );
 636                                  $this->mPage->setTimestamp( $cachedTimestamp );
 637                              }
 638                              $outputDone = true;
 639                          }
 640                      }
 641                      break;
 642                  case 3:
 643                      # This will set $this->mRevision if needed
 644                      $this->fetchContentObject();
 645  
 646                      # Are we looking at an old revision
 647                      if ( $oldid && $this->mRevision ) {
 648                          $this->setOldSubtitle( $oldid );
 649  
 650                          if ( !$this->showDeletedRevisionHeader() ) {
 651                              wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
 652                              wfProfileOut( __METHOD__ );
 653                              return;
 654                          }
 655                      }
 656  
 657                      # Ensure that UI elements requiring revision ID have
 658                      # the correct version information.
 659                      $outputPage->setRevisionId( $this->getRevIdFetched() );
 660                      # Preload timestamp to avoid a DB hit
 661                      $outputPage->setRevisionTimestamp( $this->getTimestamp() );
 662  
 663                      # Pages containing custom CSS or JavaScript get special treatment
 664                      if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
 665                          wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
 666                          $this->showCssOrJsPage();
 667                          $outputDone = true;
 668                      } elseif ( !wfRunHooks( 'ArticleContentViewCustom',
 669                              array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
 670  
 671                          # Allow extensions do their own custom view for certain pages
 672                          $outputDone = true;
 673                      } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
 674                              array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
 675  
 676                          # Allow extensions do their own custom view for certain pages
 677                          $outputDone = true;
 678                      }
 679                      break;
 680                  case 4:
 681                      # Run the parse, protected by a pool counter
 682                      wfDebug( __METHOD__ . ": doing uncached parse\n" );
 683  
 684                      $content = $this->getContentObject();
 685                      $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
 686                          $this->getRevIdFetched(), $useParserCache, $content );
 687  
 688                      if ( !$poolArticleView->execute() ) {
 689                          $error = $poolArticleView->getError();
 690                          if ( $error ) {
 691                              $outputPage->clearHTML(); // for release() errors
 692                              $outputPage->enableClientCache( false );
 693                              $outputPage->setRobotPolicy( 'noindex,nofollow' );
 694  
 695                              $errortext = $error->getWikiText( false, 'view-pool-error' );
 696                              $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
 697                          }
 698                          # Connection or timeout error
 699                          wfProfileOut( __METHOD__ );
 700                          return;
 701                      }
 702  
 703                      $this->mParserOutput = $poolArticleView->getParserOutput();
 704                      $outputPage->addParserOutput( $this->mParserOutput );
 705                      if ( $content->getRedirectTarget() ) {
 706                          $outputPage->addSubtitle(
 707                              "<span id=\"redirectsub\">" . wfMessage( 'redirectpagesub' )->parse() . "</span>"
 708                          );
 709                      }
 710  
 711                      # Don't cache a dirty ParserOutput object
 712                      if ( $poolArticleView->getIsDirty() ) {
 713                          $outputPage->setSquidMaxage( 0 );
 714                          $outputPage->addHTML( "<!-- parser cache is expired, " .
 715                              "sending anyway due to pool overload-->\n" );
 716                      }
 717  
 718                      $outputDone = true;
 719                      break;
 720                  # Should be unreachable, but just in case...
 721                  default:
 722                      break 2;
 723              }
 724          }
 725  
 726          # Get the ParserOutput actually *displayed* here.
 727          # Note that $this->mParserOutput is the *current* version output.
 728          $pOutput = ( $outputDone instanceof ParserOutput )
 729              ? $outputDone // object fetched by hook
 730              : $this->mParserOutput;
 731  
 732          # Adjust title for main page & pages with displaytitle
 733          if ( $pOutput ) {
 734              $this->adjustDisplayTitle( $pOutput );
 735          }
 736  
 737          # For the main page, overwrite the <title> element with the con-
 738          # tents of 'pagetitle-view-mainpage' instead of the default (if
 739          # that's not empty).
 740          # This message always exists because it is in the i18n files
 741          if ( $this->getTitle()->isMainPage() ) {
 742              $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
 743              if ( !$msg->isDisabled() ) {
 744                  $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
 745              }
 746          }
 747  
 748          # Check for any __NOINDEX__ tags on the page using $pOutput
 749          $policy = $this->getRobotPolicy( 'view', $pOutput );
 750          $outputPage->setIndexPolicy( $policy['index'] );
 751          $outputPage->setFollowPolicy( $policy['follow'] );
 752  
 753          $this->showViewFooter();
 754          $this->mPage->doViewUpdates( $user, $oldid );
 755  
 756          $outputPage->addModules( 'mediawiki.action.view.postEdit' );
 757  
 758          wfProfileOut( __METHOD__ );
 759      }
 760  
 761      /**
 762       * Adjust title for pages with displaytitle, -{T|}- or language conversion
 763       * @param ParserOutput $pOutput
 764       */
 765  	public function adjustDisplayTitle( ParserOutput $pOutput ) {
 766          # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
 767          $titleText = $pOutput->getTitleText();
 768          if ( strval( $titleText ) !== '' ) {
 769              $this->getContext()->getOutput()->setPageTitle( $titleText );
 770          }
 771      }
 772  
 773      /**
 774       * Show a diff page according to current request variables. For use within
 775       * Article::view() only, other callers should use the DifferenceEngine class.
 776       *
 777       * @todo Make protected
 778       */
 779  	public function showDiffPage() {
 780          $request = $this->getContext()->getRequest();
 781          $user = $this->getContext()->getUser();
 782          $diff = $request->getVal( 'diff' );
 783          $rcid = $request->getVal( 'rcid' );
 784          $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
 785          $purge = $request->getVal( 'action' ) == 'purge';
 786          $unhide = $request->getInt( 'unhide' ) == 1;
 787          $oldid = $this->getOldID();
 788  
 789          $rev = $this->getRevisionFetched();
 790  
 791          if ( !$rev ) {
 792              $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
 793              $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 );
 794              return;
 795          }
 796  
 797          $contentHandler = $rev->getContentHandler();
 798          $de = $contentHandler->createDifferenceEngine(
 799              $this->getContext(),
 800              $oldid,
 801              $diff,
 802              $rcid,
 803              $purge,
 804              $unhide
 805          );
 806  
 807          // DifferenceEngine directly fetched the revision:
 808          $this->mRevIdFetched = $de->mNewid;
 809          $de->showDiffPage( $diffOnly );
 810  
 811          // Run view updates for the newer revision being diffed (and shown
 812          // below the diff if not $diffOnly).
 813          list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
 814          // New can be false, convert it to 0 - this conveniently means the latest revision
 815          $this->mPage->doViewUpdates( $user, (int)$new );
 816      }
 817  
 818      /**
 819       * Show a page view for a page formatted as CSS or JavaScript. To be called by
 820       * Article::view() only.
 821       *
 822       * This exists mostly to serve the deprecated ShowRawCssJs hook (used to customize these views).
 823       * It has been replaced by the ContentGetParserOutput hook, which lets you do the same but with
 824       * more flexibility.
 825       *
 826       * @param bool $showCacheHint Whether to show a message telling the user
 827       *   to clear the browser cache (default: true).
 828       */
 829  	protected function showCssOrJsPage( $showCacheHint = true ) {
 830          $outputPage = $this->getContext()->getOutput();
 831  
 832          if ( $showCacheHint ) {
 833              $dir = $this->getContext()->getLanguage()->getDir();
 834              $lang = $this->getContext()->getLanguage()->getCode();
 835  
 836              $outputPage->wrapWikiMsg(
 837                  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
 838                  'clearyourcache'
 839              );
 840          }
 841  
 842          $this->fetchContentObject();
 843  
 844          if ( $this->mContentObject ) {
 845              // Give hooks a chance to customise the output
 846              if ( ContentHandler::runLegacyHooks(
 847                  'ShowRawCssJs',
 848                  array( $this->mContentObject, $this->getTitle(), $outputPage ) )
 849              ) {
 850                  // If no legacy hooks ran, display the content of the parser output, including RL modules,
 851                  // but excluding metadata like categories and language links
 852                  $po = $this->mContentObject->getParserOutput( $this->getTitle() );
 853                  $outputPage->addParserOutputContent( $po );
 854              }
 855          }
 856      }
 857  
 858      /**
 859       * Get the robot policy to be used for the current view
 860       * @param string $action The action= GET parameter
 861       * @param ParserOutput|null $pOutput
 862       * @return array The policy that should be set
 863       * @todo actions other than 'view'
 864       */
 865  	public function getRobotPolicy( $action, $pOutput = null ) {
 866          global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
 867  
 868          $ns = $this->getTitle()->getNamespace();
 869  
 870          # Don't index user and user talk pages for blocked users (bug 11443)
 871          if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
 872              $specificTarget = null;
 873              $vagueTarget = null;
 874              $titleText = $this->getTitle()->getText();
 875              if ( IP::isValid( $titleText ) ) {
 876                  $vagueTarget = $titleText;
 877              } else {
 878                  $specificTarget = $titleText;
 879              }
 880              if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
 881                  return array(
 882                      'index' => 'noindex',
 883                      'follow' => 'nofollow'
 884                  );
 885              }
 886          }
 887  
 888          if ( $this->mPage->getID() === 0 || $this->getOldID() ) {
 889              # Non-articles (special pages etc), and old revisions
 890              return array(
 891                  'index' => 'noindex',
 892                  'follow' => 'nofollow'
 893              );
 894          } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
 895              # Discourage indexing of printable versions, but encourage following
 896              return array(
 897                  'index' => 'noindex',
 898                  'follow' => 'follow'
 899              );
 900          } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
 901              # For ?curid=x urls, disallow indexing
 902              return array(
 903                  'index' => 'noindex',
 904                  'follow' => 'follow'
 905              );
 906          }
 907  
 908          # Otherwise, construct the policy based on the various config variables.
 909          $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
 910  
 911          if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
 912              # Honour customised robot policies for this namespace
 913              $policy = array_merge(
 914                  $policy,
 915                  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
 916              );
 917          }
 918          if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
 919              # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
 920              # a final sanity check that we have really got the parser output.
 921              $policy = array_merge(
 922                  $policy,
 923                  array( 'index' => $pOutput->getIndexPolicy() )
 924              );
 925          }
 926  
 927          if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
 928              # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
 929              $policy = array_merge(
 930                  $policy,
 931                  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
 932              );
 933          }
 934  
 935          return $policy;
 936      }
 937  
 938      /**
 939       * Converts a String robot policy into an associative array, to allow
 940       * merging of several policies using array_merge().
 941       * @param array|string $policy Returns empty array on null/false/'', transparent
 942       *   to already-converted arrays, converts string.
 943       * @return array 'index' => \<indexpolicy\>, 'follow' => \<followpolicy\>
 944       */
 945  	public static function formatRobotPolicy( $policy ) {
 946          if ( is_array( $policy ) ) {
 947              return $policy;
 948          } elseif ( !$policy ) {
 949              return array();
 950          }
 951  
 952          $policy = explode( ',', $policy );
 953          $policy = array_map( 'trim', $policy );
 954  
 955          $arr = array();
 956          foreach ( $policy as $var ) {
 957              if ( in_array( $var, array( 'index', 'noindex' ) ) ) {
 958                  $arr['index'] = $var;
 959              } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) {
 960                  $arr['follow'] = $var;
 961              }
 962          }
 963  
 964          return $arr;
 965      }
 966  
 967      /**
 968       * If this request is a redirect view, send "redirected from" subtitle to
 969       * the output. Returns true if the header was needed, false if this is not
 970       * a redirect view. Handles both local and remote redirects.
 971       *
 972       * @return bool
 973       */
 974  	public function showRedirectedFromHeader() {
 975          global $wgRedirectSources;
 976          $outputPage = $this->getContext()->getOutput();
 977  
 978          $request = $this->getContext()->getRequest();
 979          $rdfrom = $request->getVal( 'rdfrom' );
 980  
 981          // Construct a URL for the current page view, but with the target title
 982          $query = $request->getValues();
 983          unset( $query['rdfrom'] );
 984          unset( $query['title'] );
 985          if ( $this->getTitle()->isRedirect() ) {
 986              // Prevent double redirects
 987              $query['redirect'] = 'no';
 988          }
 989          $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
 990  
 991          if ( isset( $this->mRedirectedFrom ) ) {
 992              // This is an internally redirected page view.
 993              // We'll need a backlink to the source page for navigation.
 994              if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
 995                  $redir = Linker::linkKnown(
 996                      $this->mRedirectedFrom,
 997                      null,
 998                      array(),
 999                      array( 'redirect' => 'no' )
1000                  );
1001  
1002                  $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
1003  
1004                  // Add the script to update the displayed URL and
1005                  // set the fragment if one was specified in the redirect
1006                  $outputPage->addJsConfigVars( array(
1007                      'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1008                  ) );
1009                  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1010  
1011                  // Add a <link rel="canonical"> tag
1012                  $outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() );
1013  
1014                  // Tell the output object that the user arrived at this article through a redirect
1015                  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1016  
1017                  return true;
1018              }
1019          } elseif ( $rdfrom ) {
1020              // This is an externally redirected view, from some other wiki.
1021              // If it was reported from a trusted site, supply a backlink.
1022              if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1023                  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1024                  $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
1025  
1026                  // Add the script to update the displayed URL
1027                  $outputPage->addJsConfigVars( array(
1028                      'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1029                  ) );
1030                  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1031  
1032                  return true;
1033              }
1034          }
1035  
1036          return false;
1037      }
1038  
1039      /**
1040       * Show a header specific to the namespace currently being viewed, like
1041       * [[MediaWiki:Talkpagetext]]. For Article::view().
1042       */
1043  	public function showNamespaceHeader() {
1044          if ( $this->getTitle()->isTalkPage() ) {
1045              if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
1046                  $this->getContext()->getOutput()->wrapWikiMsg(
1047                      "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1048                      array( 'talkpageheader' )
1049                  );
1050              }
1051          }
1052      }
1053  
1054      /**
1055       * Show the footer section of an ordinary page view
1056       */
1057  	public function showViewFooter() {
1058          # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1059          if ( $this->getTitle()->getNamespace() == NS_USER_TALK
1060              && IP::isValid( $this->getTitle()->getText() )
1061          ) {
1062              $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1063          }
1064  
1065          // Show a footer allowing the user to patrol the shown revision or page if possible
1066          $patrolFooterShown = $this->showPatrolFooter();
1067  
1068          wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) );
1069      }
1070  
1071      /**
1072       * If patrol is possible, output a patrol UI box. This is called from the
1073       * footer section of ordinary page views. If patrol is not possible or not
1074       * desired, does nothing.
1075       * Side effect: When the patrol link is build, this method will call
1076       * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
1077       *
1078       * @return bool
1079       */
1080  	public function showPatrolFooter() {
1081          global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
1082  
1083          $outputPage = $this->getContext()->getOutput();
1084          $user = $this->getContext()->getUser();
1085          $cache = wfGetMainCache();
1086          $rc = false;
1087  
1088          if ( !$this->getTitle()->quickUserCan( 'patrol', $user )
1089              || !( $wgUseRCPatrol || $wgUseNPPatrol )
1090          ) {
1091              // Patrolling is disabled or the user isn't allowed to
1092              return false;
1093          }
1094  
1095          wfProfileIn( __METHOD__ );
1096  
1097          // New page patrol: Get the timestamp of the oldest revison which
1098          // the revision table holds for the given page. Then we look
1099          // whether it's within the RC lifespan and if it is, we try
1100          // to get the recentchanges row belonging to that entry
1101          // (with rc_new = 1).
1102  
1103          // Check for cached results
1104          if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) {
1105              wfProfileOut( __METHOD__ );
1106              return false;
1107          }
1108  
1109          if ( $this->mRevision
1110              && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
1111          ) {
1112              // The current revision is already older than what could be in the RC table
1113              // 6h tolerance because the RC might not be cleaned out regularly
1114              wfProfileOut( __METHOD__ );
1115              return false;
1116          }
1117  
1118          $dbr = wfGetDB( DB_SLAVE );
1119          $oldestRevisionTimestamp = $dbr->selectField(
1120              'revision',
1121              'MIN( rev_timestamp )',
1122              array( 'rev_page' => $this->getTitle()->getArticleID() ),
1123              __METHOD__
1124          );
1125  
1126          if ( $oldestRevisionTimestamp
1127              && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1128          ) {
1129              // 6h tolerance because the RC might not be cleaned out regularly
1130              $rc = RecentChange::newFromConds(
1131                  array(
1132                      'rc_new' => 1,
1133                      'rc_timestamp' => $oldestRevisionTimestamp,
1134                      'rc_namespace' => $this->getTitle()->getNamespace(),
1135                      'rc_cur_id' => $this->getTitle()->getArticleID(),
1136                      'rc_patrolled' => 0
1137                  ),
1138                  __METHOD__,
1139                  array( 'USE INDEX' => 'new_name_timestamp' )
1140              );
1141          }
1142  
1143          if ( !$rc ) {
1144              // No RC entry around
1145  
1146              // Cache the information we gathered above in case we can't patrol
1147              // Don't cache in case we can patrol as this could change
1148              $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' );
1149  
1150              wfProfileOut( __METHOD__ );
1151              return false;
1152          }
1153  
1154          if ( $rc->getPerformer()->getName() == $user->getName() ) {
1155              // Don't show a patrol link for own creations. If the user could
1156              // patrol them, they already would be patrolled
1157              wfProfileOut( __METHOD__ );
1158              return false;
1159          }
1160  
1161          $rcid = $rc->getAttribute( 'rc_id' );
1162  
1163          $token = $user->getEditToken( $rcid );
1164  
1165          $outputPage->preventClickjacking();
1166          if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
1167              $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1168          }
1169  
1170          $link = Linker::linkKnown(
1171              $this->getTitle(),
1172              wfMessage( 'markaspatrolledtext' )->escaped(),
1173              array(),
1174              array(
1175                  'action' => 'markpatrolled',
1176                  'rcid' => $rcid,
1177                  'token' => $token,
1178              )
1179          );
1180  
1181          $outputPage->addHTML(
1182              "<div class='patrollink'>" .
1183                  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1184              '</div>'
1185          );
1186  
1187          wfProfileOut( __METHOD__ );
1188          return true;
1189      }
1190  
1191      /**
1192       * Show the error text for a missing article. For articles in the MediaWiki
1193       * namespace, show the default message text. To be called from Article::view().
1194       */
1195  	public function showMissingArticle() {
1196          global $wgSend404Code;
1197  
1198          $outputPage = $this->getContext()->getOutput();
1199          // Whether the page is a root user page of an existing user (but not a subpage)
1200          $validUserPage = false;
1201  
1202          $title = $this->getTitle();
1203  
1204          # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1205          if ( $title->getNamespace() == NS_USER
1206              || $title->getNamespace() == NS_USER_TALK
1207          ) {
1208              $parts = explode( '/', $title->getText() );
1209              $rootPart = $parts[0];
1210              $user = User::newFromName( $rootPart, false /* allow IP users*/ );
1211              $ip = User::isIP( $rootPart );
1212              $block = Block::newFromTarget( $user, $user );
1213  
1214              if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1215                  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1216                      array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) );
1217              } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked
1218                  LogEventsList::showLogExtract(
1219                      $outputPage,
1220                      'block',
1221                      MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
1222                      '',
1223                      array(
1224                          'lim' => 1,
1225                          'showIfEmpty' => false,
1226                          'msgKey' => array(
1227                              'blocked-notice-logextract',
1228                              $user->getName() # Support GENDER in notice
1229                          )
1230                      )
1231                  );
1232                  $validUserPage = !$title->isSubpage();
1233              } else {
1234                  $validUserPage = !$title->isSubpage();
1235              }
1236          }
1237  
1238          wfRunHooks( 'ShowMissingArticle', array( $this ) );
1239  
1240          // Give extensions a chance to hide their (unrelated) log entries
1241          $logTypes = array( 'delete', 'move' );
1242          $conds = array( "log_action != 'revision'" );
1243          wfRunHooks( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) );
1244  
1245          # Show delete and move logs
1246          $member = $title->getNamespace() . ':' . $title->getDBkey();
1247          // @todo: move optimization to showLogExtract()?
1248          if ( BloomCache::get( 'main' )->check( wfWikiId(), 'TitleHasLogs', $member ) ) {
1249              LogEventsList::showLogExtract( $outputPage, $logTypes, $title, '',
1250                  array( 'lim' => 10,
1251                      'conds' => $conds,
1252                      'showIfEmpty' => false,
1253                      'msgKey' => array( 'moveddeleted-notice' ) )
1254              );
1255          }
1256  
1257          if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1258              // If there's no backing content, send a 404 Not Found
1259              // for better machine handling of broken links.
1260              $this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" );
1261          }
1262  
1263          // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1264          $policy = $this->getRobotPolicy( 'view' );
1265          $outputPage->setIndexPolicy( $policy['index'] );
1266          $outputPage->setFollowPolicy( $policy['follow'] );
1267  
1268          $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
1269  
1270          if ( !$hookResult ) {
1271              return;
1272          }
1273  
1274          # Show error message
1275          $oldid = $this->getOldID();
1276          if ( $oldid ) {
1277              $text = wfMessage( 'missing-revision', $oldid )->plain();
1278          } elseif ( $title->getNamespace() === NS_MEDIAWIKI ) {
1279              // Use the default message text
1280              $text = $title->getDefaultMessageText();
1281          } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1282              && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1283          ) {
1284              $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1285              $text = wfMessage( $message )->plain();
1286          } else {
1287              $text = wfMessage( 'noarticletext-nopermission' )->plain();
1288          }
1289          $text = "<div class='noarticletext'>\n$text\n</div>";
1290  
1291          $outputPage->addWikiText( $text );
1292      }
1293  
1294      /**
1295       * If the revision requested for view is deleted, check permissions.
1296       * Send either an error message or a warning header to the output.
1297       *
1298       * @return bool True if the view is allowed, false if not.
1299       */
1300  	public function showDeletedRevisionHeader() {
1301          if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
1302              // Not deleted
1303              return true;
1304          }
1305  
1306          $outputPage = $this->getContext()->getOutput();
1307          $user = $this->getContext()->getUser();
1308          // If the user is not allowed to see it...
1309          if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
1310              $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1311                  'rev-deleted-text-permission' );
1312  
1313              return false;
1314          // If the user needs to confirm that they want to see it...
1315          } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1316              # Give explanation and add a link to view the revision...
1317              $oldid = intval( $this->getOldID() );
1318              $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1319              $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1320                  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1321              $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1322                  array( $msg, $link ) );
1323  
1324              return false;
1325          // We are allowed to see...
1326          } else {
1327              $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1328                  'rev-suppressed-text-view' : 'rev-deleted-text-view';
1329              $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1330  
1331              return true;
1332          }
1333      }
1334  
1335      /**
1336       * Generate the navigation links when browsing through an article revisions
1337       * It shows the information as:
1338       *   Revision as of \<date\>; view current revision
1339       *   \<- Previous version | Next Version -\>
1340       *
1341       * @param int $oldid Revision ID of this article revision
1342       */
1343  	public function setOldSubtitle( $oldid = 0 ) {
1344          if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
1345              return;
1346          }
1347  
1348          $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1;
1349  
1350          # Cascade unhide param in links for easy deletion browsing
1351          $extraParams = array();
1352          if ( $unhide ) {
1353              $extraParams['unhide'] = 1;
1354          }
1355  
1356          if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1357              $revision = $this->mRevision;
1358          } else {
1359              $revision = Revision::newFromId( $oldid );
1360          }
1361  
1362          $timestamp = $revision->getTimestamp();
1363  
1364          $current = ( $oldid == $this->mPage->getLatest() );
1365          $language = $this->getContext()->getLanguage();
1366          $user = $this->getContext()->getUser();
1367  
1368          $td = $language->userTimeAndDate( $timestamp, $user );
1369          $tddate = $language->userDate( $timestamp, $user );
1370          $tdtime = $language->userTime( $timestamp, $user );
1371  
1372          # Show user links if allowed to see them. If hidden, then show them only if requested...
1373          $userlinks = Linker::revUserTools( $revision, !$unhide );
1374  
1375          $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
1376              ? 'revision-info-current'
1377              : 'revision-info';
1378  
1379          $outputPage = $this->getContext()->getOutput();
1380          $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
1381              $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
1382              $tdtime, $revision->getUserText() )->rawParams( Linker::revComment( $revision, true, true ) )->parse() . "</div>" );
1383  
1384          $lnk = $current
1385              ? wfMessage( 'currentrevisionlink' )->escaped()
1386              : Linker::linkKnown(
1387                  $this->getTitle(),
1388                  wfMessage( 'currentrevisionlink' )->escaped(),
1389                  array(),
1390                  $extraParams
1391              );
1392          $curdiff = $current
1393              ? wfMessage( 'diff' )->escaped()
1394              : Linker::linkKnown(
1395                  $this->getTitle(),
1396                  wfMessage( 'diff' )->escaped(),
1397                  array(),
1398                  array(
1399                      'diff' => 'cur',
1400                      'oldid' => $oldid
1401                  ) + $extraParams
1402              );
1403          $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1404          $prevlink = $prev
1405              ? Linker::linkKnown(
1406                  $this->getTitle(),
1407                  wfMessage( 'previousrevision' )->escaped(),
1408                  array(),
1409                  array(
1410                      'direction' => 'prev',
1411                      'oldid' => $oldid
1412                  ) + $extraParams
1413              )
1414              : wfMessage( 'previousrevision' )->escaped();
1415          $prevdiff = $prev
1416              ? Linker::linkKnown(
1417                  $this->getTitle(),
1418                  wfMessage( 'diff' )->escaped(),
1419                  array(),
1420                  array(
1421                      'diff' => 'prev',
1422                      'oldid' => $oldid
1423                  ) + $extraParams
1424              )
1425              : wfMessage( 'diff' )->escaped();
1426          $nextlink = $current
1427              ? wfMessage( 'nextrevision' )->escaped()
1428              : Linker::linkKnown(
1429                  $this->getTitle(),
1430                  wfMessage( 'nextrevision' )->escaped(),
1431                  array(),
1432                  array(
1433                      'direction' => 'next',
1434                      'oldid' => $oldid
1435                  ) + $extraParams
1436              );
1437          $nextdiff = $current
1438              ? wfMessage( 'diff' )->escaped()
1439              : Linker::linkKnown(
1440                  $this->getTitle(),
1441                  wfMessage( 'diff' )->escaped(),
1442                  array(),
1443                  array(
1444                      'diff' => 'next',
1445                      'oldid' => $oldid
1446                  ) + $extraParams
1447              );
1448  
1449          $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
1450          if ( $cdel !== '' ) {
1451              $cdel .= ' ';
1452          }
1453  
1454          $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
1455              wfMessage( 'revision-nav' )->rawParams(
1456                  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1457              )->escaped() . "</div>" );
1458      }
1459  
1460      /**
1461       * Return the HTML for the top of a redirect page
1462       *
1463       * Chances are you should just be using the ParserOutput from
1464       * WikitextContent::getParserOutput instead of calling this for redirects.
1465       *
1466       * @param Title|array $target Destination(s) to redirect
1467       * @param bool $appendSubtitle [optional]
1468       * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
1469       * @return string Containing HTML with redirect link
1470       */
1471  	public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1472          $lang = $this->getTitle()->getPageLanguage();
1473          $out = $this->getContext()->getOutput();
1474          if ( $appendSubtitle ) {
1475              $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
1476          }
1477          $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1478          return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1479      }
1480  
1481      /**
1482       * Return the HTML for the top of a redirect page
1483       *
1484       * Chances are you should just be using the ParserOutput from
1485       * WikitextContent::getParserOutput instead of calling this for redirects.
1486       *
1487       * @since 1.23
1488       * @param Language $lang
1489       * @param Title|array $target Destination(s) to redirect
1490       * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
1491       * @return string Containing HTML with redirect link
1492       */
1493  	public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1494          if ( !is_array( $target ) ) {
1495              $target = array( $target );
1496          }
1497  
1498          $html = '<ul class="redirectText">';
1499          /** @var Title $title */
1500          foreach ( $target as $title ) {
1501              $html .= '<li>' . Linker::link(
1502                  $title,
1503                  htmlspecialchars( $title->getFullText() ),
1504                  array(),
1505                  // Automatically append redirect=no to each link, since most of them are
1506                  // redirect pages themselves.
1507                  array( 'redirect' => 'no' ),
1508                  ( $forceKnown ? array( 'known', 'noclasses' ) : array() )
1509              ) . '</li>';
1510          }
1511  
1512          $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->text();
1513  
1514          return '<div class="redirectMsg">' .
1515              '<p>' . $redirectToText . '</p>' .
1516              $html .
1517              '</div>';
1518      }
1519  
1520      /**
1521       * Handle action=render
1522       */
1523  	public function render() {
1524          $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1525          $this->getContext()->getOutput()->setArticleBodyOnly( true );
1526          $this->getContext()->getOutput()->enableSectionEditLinks( false );
1527          $this->view();
1528      }
1529  
1530      /**
1531       * action=protect handler
1532       */
1533  	public function protect() {
1534          $form = new ProtectionForm( $this );
1535          $form->execute();
1536      }
1537  
1538      /**
1539       * action=unprotect handler (alias)
1540       */
1541  	public function unprotect() {
1542          $this->protect();
1543      }
1544  
1545      /**
1546       * UI entry point for page deletion
1547       */
1548  	public function delete() {
1549          # This code desperately needs to be totally rewritten
1550  
1551          $title = $this->getTitle();
1552          $user = $this->getContext()->getUser();
1553  
1554          # Check permissions
1555          $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1556          if ( count( $permissionErrors ) ) {
1557              throw new PermissionsError( 'delete', $permissionErrors );
1558          }
1559  
1560          # Read-only check...
1561          if ( wfReadOnly() ) {
1562              throw new ReadOnlyError;
1563          }
1564  
1565          # Better double-check that it hasn't been deleted yet!
1566          $this->mPage->loadPageData( 'fromdbmaster' );
1567          if ( !$this->mPage->exists() ) {
1568              $deleteLogPage = new LogPage( 'delete' );
1569              $outputPage = $this->getContext()->getOutput();
1570              $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) );
1571              $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1572                      array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) )
1573                  );
1574              $outputPage->addHTML(
1575                  Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1576              );
1577              LogEventsList::showLogExtract(
1578                  $outputPage,
1579                  'delete',
1580                  $title
1581              );
1582  
1583              return;
1584          }
1585  
1586          $request = $this->getContext()->getRequest();
1587          $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1588          $deleteReason = $request->getText( 'wpReason' );
1589  
1590          if ( $deleteReasonList == 'other' ) {
1591              $reason = $deleteReason;
1592          } elseif ( $deleteReason != '' ) {
1593              // Entry from drop down menu + additional comment
1594              $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1595              $reason = $deleteReasonList . $colonseparator . $deleteReason;
1596          } else {
1597              $reason = $deleteReasonList;
1598          }
1599  
1600          if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1601              array( 'delete', $this->getTitle()->getPrefixedText() ) )
1602          ) {
1603              # Flag to hide all contents of the archived revisions
1604              $suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
1605  
1606              $this->doDelete( $reason, $suppress );
1607  
1608              WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1609  
1610              return;
1611          }
1612  
1613          // Generate deletion reason
1614          $hasHistory = false;
1615          if ( !$reason ) {
1616              try {
1617                  $reason = $this->generateReason( $hasHistory );
1618              } catch ( MWException $e ) {
1619                  # if a page is horribly broken, we still want to be able to
1620                  # delete it. So be lenient about errors here.
1621                  wfDebug( "Error while building auto delete summary: $e" );
1622                  $reason = '';
1623              }
1624          }
1625  
1626          // If the page has a history, insert a warning
1627          if ( $hasHistory ) {
1628              $title = $this->getTitle();
1629  
1630              // The following can use the real revision count as this is only being shown for users that can delete
1631              // this page.
1632              // This, as a side-effect, also makes sure that the following query isn't being run for pages with a
1633              // larger history, unless the user has the 'bigdelete' right (and is about to delete this page).
1634              $dbr = wfGetDB( DB_SLAVE );
1635              $revisions = $edits = (int)$dbr->selectField(
1636                  'revision',
1637                  'COUNT(rev_page)',
1638                  array( 'rev_page' => $title->getArticleID() ),
1639                  __METHOD__
1640              );
1641  
1642              // @todo FIXME: i18n issue/patchwork message
1643              $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' .
1644                  wfMessage( 'historywarning' )->numParams( $revisions )->parse() .
1645                  wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title,
1646                      wfMessage( 'history' )->escaped(),
1647                      array( 'rel' => 'archives' ),
1648                      array( 'action' => 'history' ) ) .
1649                  '</strong>'
1650              );
1651  
1652              if ( $title->isBigDeletion() ) {
1653                  global $wgDeleteRevisionsLimit;
1654                  $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1655                      array(
1656                          'delete-warning-toobig',
1657                          $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1658                      )
1659                  );
1660              }
1661          }
1662  
1663          $this->confirmDelete( $reason );
1664      }
1665  
1666      /**
1667       * Output deletion confirmation dialog
1668       * @todo FIXME: Move to another file?
1669       * @param string $reason Prefilled reason
1670       */
1671  	public function confirmDelete( $reason ) {
1672          wfDebug( "Article::confirmDelete\n" );
1673  
1674          $title = $this->getTitle();
1675          $outputPage = $this->getContext()->getOutput();
1676          $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1677          $outputPage->addBacklinkSubtitle( $title );
1678          $outputPage->setRobotPolicy( 'noindex,nofollow' );
1679          $backlinkCache = $title->getBacklinkCache();
1680          if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1681              $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1682                  'deleting-backlinks-warning' );
1683          }
1684          $outputPage->addWikiMsg( 'confirmdeletetext' );
1685  
1686          wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) );
1687  
1688          $user = $this->getContext()->getUser();
1689  
1690          if ( $user->isAllowed( 'suppressrevision' ) ) {
1691              $suppress = "<tr id=\"wpDeleteSuppressRow\">
1692                      <td></td>
1693                      <td class='mw-input'><strong>" .
1694                          Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
1695                              'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
1696                      "</strong></td>
1697                  </tr>";
1698          } else {
1699              $suppress = '';
1700          }
1701          $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1702  
1703          $form = Xml::openElement( 'form', array( 'method' => 'post',
1704              'action' => $title->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
1705              Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
1706              Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) .
1707              Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
1708              "<tr id=\"wpDeleteReasonListRow\">
1709                  <td class='mw-label'>" .
1710                      Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
1711                  "</td>
1712                  <td class='mw-input'>" .
1713                      Xml::listDropDown(
1714                          'wpDeleteReasonList',
1715                          wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
1716                          wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
1717                          '',
1718                          'wpReasonDropDown',
1719                          1
1720                      ) .
1721                  "</td>
1722              </tr>
1723              <tr id=\"wpDeleteReasonRow\">
1724                  <td class='mw-label'>" .
1725                      Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
1726                  "</td>
1727                  <td class='mw-input'>" .
1728                  Html::input( 'wpReason', $reason, 'text', array(
1729                      'size' => '60',
1730                      'maxlength' => '255',
1731                      'tabindex' => '2',
1732                      'id' => 'wpReason',
1733                      'autofocus'
1734                  ) ) .
1735                  "</td>
1736              </tr>";
1737  
1738          # Disallow watching if user is not logged in
1739          if ( $user->isLoggedIn() ) {
1740              $form .= "
1741              <tr>
1742                  <td></td>
1743                  <td class='mw-input'>" .
1744                      Xml::checkLabel( wfMessage( 'watchthis' )->text(),
1745                          'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
1746                  "</td>
1747              </tr>";
1748          }
1749  
1750          $form .= "
1751              $suppress
1752              <tr>
1753                  <td></td>
1754                  <td class='mw-submit'>" .
1755                      Xml::submitButton( wfMessage( 'deletepage' )->text(),
1756                          array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
1757                  "</td>
1758              </tr>" .
1759              Xml::closeElement( 'table' ) .
1760              Xml::closeElement( 'fieldset' ) .
1761              Html::hidden(
1762                  'wpEditToken',
1763                  $user->getEditToken( array( 'delete', $title->getPrefixedText() ) )
1764              ) .
1765              Xml::closeElement( 'form' );
1766  
1767              if ( $user->isAllowed( 'editinterface' ) ) {
1768                  $dropdownTitle = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
1769                  $link = Linker::link(
1770                      $dropdownTitle,
1771                      wfMessage( 'delete-edit-reasonlist' )->escaped(),
1772                      array(),
1773                      array( 'action' => 'edit' )
1774                  );
1775                  $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
1776              }
1777  
1778          $outputPage->addHTML( $form );
1779  
1780          $deleteLogPage = new LogPage( 'delete' );
1781          $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1782          LogEventsList::showLogExtract( $outputPage, 'delete', $title );
1783      }
1784  
1785      /**
1786       * Perform a deletion and output success or failure messages
1787       * @param string $reason
1788       * @param bool $suppress
1789       */
1790  	public function doDelete( $reason, $suppress = false ) {
1791          $error = '';
1792          $outputPage = $this->getContext()->getOutput();
1793          $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error );
1794  
1795          if ( $status->isGood() ) {
1796              $deleted = $this->getTitle()->getPrefixedText();
1797  
1798              $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
1799              $outputPage->setRobotPolicy( 'noindex,nofollow' );
1800  
1801              $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
1802  
1803              $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1804              $outputPage->returnToMain( false );
1805          } else {
1806              $outputPage->setPageTitle(
1807                  wfMessage( 'cannotdelete-title',
1808                      $this->getTitle()->getPrefixedText() )
1809              );
1810  
1811              if ( $error == '' ) {
1812                  $outputPage->addWikiText(
1813                      "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
1814                  );
1815                  $deleteLogPage = new LogPage( 'delete' );
1816                  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1817  
1818                  LogEventsList::showLogExtract(
1819                      $outputPage,
1820                      'delete',
1821                      $this->getTitle()
1822                  );
1823              } else {
1824                  $outputPage->addHTML( $error );
1825              }
1826          }
1827      }
1828  
1829      /* Caching functions */
1830  
1831      /**
1832       * checkLastModified returns true if it has taken care of all
1833       * output to the client that is necessary for this request.
1834       * (that is, it has sent a cached version of the page)
1835       *
1836       * @return bool True if cached version send, false otherwise
1837       */
1838  	protected function tryFileCache() {
1839          static $called = false;
1840  
1841          if ( $called ) {
1842              wfDebug( "Article::tryFileCache(): called twice!?\n" );
1843              return false;
1844          }
1845  
1846          $called = true;
1847          if ( $this->isFileCacheable() ) {
1848              $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1849              if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1850                  wfDebug( "Article::tryFileCache(): about to load file\n" );
1851                  $cache->loadFromFileCache( $this->getContext() );
1852                  return true;
1853              } else {
1854                  wfDebug( "Article::tryFileCache(): starting buffer\n" );
1855                  ob_start( array( &$cache, 'saveToFileCache' ) );
1856              }
1857          } else {
1858              wfDebug( "Article::tryFileCache(): not cacheable\n" );
1859          }
1860  
1861          return false;
1862      }
1863  
1864      /**
1865       * Check if the page can be cached
1866       * @return bool
1867       */
1868  	public function isFileCacheable() {
1869          $cacheable = false;
1870  
1871          if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
1872              $cacheable = $this->mPage->getID()
1873                  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1874              // Extension may have reason to disable file caching on some pages.
1875              if ( $cacheable ) {
1876                  $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
1877              }
1878          }
1879  
1880          return $cacheable;
1881      }
1882  
1883      /**#@-*/
1884  
1885      /**
1886       * Lightweight method to get the parser output for a page, checking the parser cache
1887       * and so on. Doesn't consider most of the stuff that WikiPage::view is forced to
1888       * consider, so it's not appropriate to use there.
1889       *
1890       * @since 1.16 (r52326) for LiquidThreads
1891       *
1892       * @param int|null $oldid Revision ID or null
1893       * @param User $user The relevant user
1894       * @return ParserOutput|bool ParserOutput or false if the given revision ID is not found
1895       */
1896  	public function getParserOutput( $oldid = null, User $user = null ) {
1897          //XXX: bypasses mParserOptions and thus setParserOptions()
1898  
1899          if ( $user === null ) {
1900              $parserOptions = $this->getParserOptions();
1901          } else {
1902              $parserOptions = $this->mPage->makeParserOptions( $user );
1903          }
1904  
1905          return $this->mPage->getParserOutput( $parserOptions, $oldid );
1906      }
1907  
1908      /**
1909       * Override the ParserOptions used to render the primary article wikitext.
1910       *
1911       * @param ParserOptions $options
1912       * @throws MWException If the parser options where already initialized.
1913       */
1914  	public function setParserOptions( ParserOptions $options ) {
1915          if ( $this->mParserOptions ) {
1916              throw new MWException( "can't change parser options after they have already been set" );
1917          }
1918  
1919          // clone, so if $options is modified later, it doesn't confuse the parser cache.
1920          $this->mParserOptions = clone $options;
1921      }
1922  
1923      /**
1924       * Get parser options suitable for rendering the primary article wikitext
1925       * @return ParserOptions
1926       */
1927  	public function getParserOptions() {
1928          if ( !$this->mParserOptions ) {
1929              $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
1930          }
1931          // Clone to allow modifications of the return value without affecting cache
1932          return clone $this->mParserOptions;
1933      }
1934  
1935      /**
1936       * Sets the context this Article is executed in
1937       *
1938       * @param IContextSource $context
1939       * @since 1.18
1940       */
1941  	public function setContext( $context ) {
1942          $this->mContext = $context;
1943      }
1944  
1945      /**
1946       * Gets the context this Article is executed in
1947       *
1948       * @return IContextSource
1949       * @since 1.18
1950       */
1951  	public function getContext() {
1952          if ( $this->mContext instanceof IContextSource ) {
1953              return $this->mContext;
1954          } else {
1955              wfDebug( __METHOD__ . " called and \$mContext is null. " .
1956                  "Return RequestContext::getMain(); for sanity\n" );
1957              return RequestContext::getMain();
1958          }
1959      }
1960  
1961      /**
1962       * Use PHP's magic __get handler to handle accessing of
1963       * raw WikiPage fields for backwards compatibility.
1964       *
1965       * @param string $fname Field name
1966       * @return mixed
1967       */
1968  	public function __get( $fname ) {
1969          if ( property_exists( $this->mPage, $fname ) ) {
1970              #wfWarn( "Access to raw $fname field " . __CLASS__ );
1971              return $this->mPage->$fname;
1972          }
1973          trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
1974      }
1975  
1976      /**
1977       * Use PHP's magic __set handler to handle setting of
1978       * raw WikiPage fields for backwards compatibility.
1979       *
1980       * @param string $fname Field name
1981       * @param mixed $fvalue New value
1982       */
1983  	public function __set( $fname, $fvalue ) {
1984          if ( property_exists( $this->mPage, $fname ) ) {
1985              #wfWarn( "Access to raw $fname field of " . __CLASS__ );
1986              $this->mPage->$fname = $fvalue;
1987          // Note: extensions may want to toss on new fields
1988          } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
1989              $this->mPage->$fname = $fvalue;
1990          } else {
1991              trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
1992          }
1993      }
1994  
1995      /**
1996       * Use PHP's magic __call handler to transform instance calls to
1997       * WikiPage functions for backwards compatibility.
1998       *
1999       * @param string $fname Name of called method
2000       * @param array $args Arguments to the method
2001       * @return mixed
2002       */
2003  	public function __call( $fname, $args ) {
2004          if ( is_callable( array( $this->mPage, $fname ) ) ) {
2005              #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" );
2006              return call_user_func_array( array( $this->mPage, $fname ), $args );
2007          }
2008          trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
2009      }
2010  
2011      // ****** B/C functions to work-around PHP silliness with __call and references ****** //
2012  
2013      /**
2014       * @param array $limit
2015       * @param array $expiry
2016       * @param bool $cascade
2017       * @param string $reason
2018       * @param User $user
2019       * @return Status
2020       */
2021  	public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2022          $reason, User $user
2023      ) {
2024          return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2025      }
2026  
2027      /**
2028       * @param array $limit
2029       * @param string $reason
2030       * @param int $cascade
2031       * @param array $expiry
2032       * @return bool
2033       */
2034  	public function updateRestrictions( $limit = array(), $reason = '',
2035          &$cascade = 0, $expiry = array()
2036      ) {
2037          return $this->mPage->doUpdateRestrictions(
2038              $limit,
2039              $expiry,
2040              $cascade,
2041              $reason,
2042              $this->getContext()->getUser()
2043          );
2044      }
2045  
2046      /**
2047       * @param string $reason
2048       * @param bool $suppress
2049       * @param int $id
2050       * @param bool $commit
2051       * @param string $error
2052       * @return bool
2053       */
2054  	public function doDeleteArticle( $reason, $suppress = false, $id = 0,
2055          $commit = true, &$error = ''
2056      ) {
2057          return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
2058      }
2059  
2060      /**
2061       * @param string $fromP
2062       * @param string $summary
2063       * @param string $token
2064       * @param bool $bot
2065       * @param array $resultDetails
2066       * @param User|null $user
2067       * @return array
2068       */
2069  	public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2070          $user = is_null( $user ) ? $this->getContext()->getUser() : $user;
2071          return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2072      }
2073  
2074      /**
2075       * @param string $fromP
2076       * @param string $summary
2077       * @param bool $bot
2078       * @param array $resultDetails
2079       * @param User|null $guser
2080       * @return array
2081       */
2082  	public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2083          $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser;
2084          return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2085      }
2086  
2087      /**
2088       * @param bool $hasHistory
2089       * @return mixed
2090       */
2091  	public function generateReason( &$hasHistory ) {
2092          $title = $this->mPage->getTitle();
2093          $handler = ContentHandler::getForTitle( $title );
2094          return $handler->getAutoDeleteReason( $title, $hasHistory );
2095      }
2096  
2097      // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
2098  
2099      /**
2100       * @return array
2101       *
2102       * @deprecated since 1.24, use WikiPage::selectFields() instead
2103       */
2104  	public static function selectFields() {
2105          wfDeprecated( __METHOD__, '1.24' );
2106          return WikiPage::selectFields();
2107      }
2108  
2109      /**
2110       * @param Title $title
2111       *
2112       * @deprecated since 1.24, use WikiPage::onArticleCreate() instead
2113       */
2114  	public static function onArticleCreate( $title ) {
2115          wfDeprecated( __METHOD__, '1.24' );
2116          WikiPage::onArticleCreate( $title );
2117      }
2118  
2119      /**
2120       * @param Title $title
2121       *
2122       * @deprecated since 1.24, use WikiPage::onArticleDelete() instead
2123       */
2124  	public static function onArticleDelete( $title ) {
2125          wfDeprecated( __METHOD__, '1.24' );
2126          WikiPage::onArticleDelete( $title );
2127      }
2128  
2129      /**
2130       * @param Title $title
2131       *
2132       * @deprecated since 1.24, use WikiPage::onArticleEdit() instead
2133       */
2134  	public static function onArticleEdit( $title ) {
2135          wfDeprecated( __METHOD__, '1.24' );
2136          WikiPage::onArticleEdit( $title );
2137      }
2138  
2139      /**
2140       * @param string $oldtext
2141       * @param string $newtext
2142       * @param int $flags
2143       * @return string
2144       * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
2145       */
2146  	public static function getAutosummary( $oldtext, $newtext, $flags ) {
2147          return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
2148      }
2149      // ******
2150  }


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