MediaWiki  REL1_24
Article.php
Go to the documentation of this file.
00001 <?php
00036 class Article implements Page {
00038     protected $mContext;
00039 
00041     protected $mPage;
00042 
00044     public $mParserOptions;
00045 
00050     public $mContent;
00051 
00056     public $mContentObject;
00057 
00059     public $mContentLoaded = false;
00060 
00062     public $mOldId;
00063 
00065     public $mRedirectedFrom = null;
00066 
00068     public $mRedirectUrl = false;
00069 
00071     public $mRevIdFetched = 0;
00072 
00074     public $mRevision = null;
00075 
00077     public $mParserOutput;
00078 
00084     public function __construct( Title $title, $oldId = null ) {
00085         $this->mOldId = $oldId;
00086         $this->mPage = $this->newPage( $title );
00087     }
00088 
00093     protected function newPage( Title $title ) {
00094         return new WikiPage( $title );
00095     }
00096 
00102     public static function newFromID( $id ) {
00103         $t = Title::newFromID( $id );
00104         # @todo FIXME: Doesn't inherit right
00105         return $t == null ? null : new self( $t );
00106         # return $t == null ? null : new static( $t ); // PHP 5.3
00107     }
00108 
00116     public static function newFromTitle( $title, IContextSource $context ) {
00117         if ( NS_MEDIA == $title->getNamespace() ) {
00118             // FIXME: where should this go?
00119             $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
00120         }
00121 
00122         $page = null;
00123         wfRunHooks( 'ArticleFromTitle', array( &$title, &$page, $context ) );
00124         if ( !$page ) {
00125             switch ( $title->getNamespace() ) {
00126                 case NS_FILE:
00127                     $page = new ImagePage( $title );
00128                     break;
00129                 case NS_CATEGORY:
00130                     $page = new CategoryPage( $title );
00131                     break;
00132                 default:
00133                     $page = new Article( $title );
00134             }
00135         }
00136         $page->setContext( $context );
00137 
00138         return $page;
00139     }
00140 
00148     public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
00149         $article = self::newFromTitle( $page->getTitle(), $context );
00150         $article->mPage = $page; // override to keep process cached vars
00151         return $article;
00152     }
00153 
00159     public function setRedirectedFrom( Title $from ) {
00160         $this->mRedirectedFrom = $from;
00161     }
00162 
00168     public function getTitle() {
00169         return $this->mPage->getTitle();
00170     }
00171 
00178     public function getPage() {
00179         return $this->mPage;
00180     }
00181 
00185     public function clear() {
00186         $this->mContentLoaded = false;
00187 
00188         $this->mRedirectedFrom = null; # Title object if set
00189         $this->mRevIdFetched = 0;
00190         $this->mRedirectUrl = false;
00191 
00192         $this->mPage->clear();
00193     }
00194 
00207     public function getContent() {
00208         ContentHandler::deprecated( __METHOD__, '1.21' );
00209         $content = $this->getContentObject();
00210         return ContentHandler::getContentText( $content );
00211     }
00212 
00228     protected function getContentObject() {
00229         wfProfileIn( __METHOD__ );
00230 
00231         if ( $this->mPage->getID() === 0 ) {
00232             # If this is a MediaWiki:x message, then load the messages
00233             # and return the message value for x.
00234             if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
00235                 $text = $this->getTitle()->getDefaultMessageText();
00236                 if ( $text === false ) {
00237                     $text = '';
00238                 }
00239 
00240                 $content = ContentHandler::makeContent( $text, $this->getTitle() );
00241             } else {
00242                 $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
00243                 $content = new MessageContent( $message, null, 'parsemag' );
00244             }
00245         } else {
00246             $this->fetchContentObject();
00247             $content = $this->mContentObject;
00248         }
00249 
00250         wfProfileOut( __METHOD__ );
00251         return $content;
00252     }
00253 
00257     public function getOldID() {
00258         if ( is_null( $this->mOldId ) ) {
00259             $this->mOldId = $this->getOldIDFromRequest();
00260         }
00261 
00262         return $this->mOldId;
00263     }
00264 
00270     public function getOldIDFromRequest() {
00271         $this->mRedirectUrl = false;
00272 
00273         $request = $this->getContext()->getRequest();
00274         $oldid = $request->getIntOrNull( 'oldid' );
00275 
00276         if ( $oldid === null ) {
00277             return 0;
00278         }
00279 
00280         if ( $oldid !== 0 ) {
00281             # Load the given revision and check whether the page is another one.
00282             # In that case, update this instance to reflect the change.
00283             if ( $oldid === $this->mPage->getLatest() ) {
00284                 $this->mRevision = $this->mPage->getRevision();
00285             } else {
00286                 $this->mRevision = Revision::newFromId( $oldid );
00287                 if ( $this->mRevision !== null ) {
00288                     // Revision title doesn't match the page title given?
00289                     if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
00290                         $function = array( get_class( $this->mPage ), 'newFromID' );
00291                         $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
00292                     }
00293                 }
00294             }
00295         }
00296 
00297         if ( $request->getVal( 'direction' ) == 'next' ) {
00298             $nextid = $this->getTitle()->getNextRevisionID( $oldid );
00299             if ( $nextid ) {
00300                 $oldid = $nextid;
00301                 $this->mRevision = null;
00302             } else {
00303                 $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
00304             }
00305         } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
00306             $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
00307             if ( $previd ) {
00308                 $oldid = $previd;
00309                 $this->mRevision = null;
00310             }
00311         }
00312 
00313         return $oldid;
00314     }
00315 
00321     function loadContent() {
00322         wfDeprecated( __METHOD__, '1.19' );
00323         $this->fetchContent();
00324     }
00325 
00340     function fetchContent() { #BC cruft!
00341         ContentHandler::deprecated( __METHOD__, '1.21' );
00342 
00343         if ( $this->mContentLoaded && $this->mContent ) {
00344             return $this->mContent;
00345         }
00346 
00347         wfProfileIn( __METHOD__ );
00348 
00349         $content = $this->fetchContentObject();
00350 
00351         if ( !$content ) {
00352             wfProfileOut( __METHOD__ );
00353             return false;
00354         }
00355 
00356         // @todo Get rid of mContent everywhere!
00357         $this->mContent = ContentHandler::getContentText( $content );
00358         ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
00359 
00360         wfProfileOut( __METHOD__ );
00361 
00362         return $this->mContent;
00363     }
00364 
00377     protected function fetchContentObject() {
00378         if ( $this->mContentLoaded ) {
00379             return $this->mContentObject;
00380         }
00381 
00382         wfProfileIn( __METHOD__ );
00383 
00384         $this->mContentLoaded = true;
00385         $this->mContent = null;
00386 
00387         $oldid = $this->getOldID();
00388 
00389         # Pre-fill content with error message so that if something
00390         # fails we'll have something telling us what we intended.
00391         //XXX: this isn't page content but a UI message. horrible.
00392         $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() );
00393 
00394         if ( $oldid ) {
00395             # $this->mRevision might already be fetched by getOldIDFromRequest()
00396             if ( !$this->mRevision ) {
00397                 $this->mRevision = Revision::newFromId( $oldid );
00398                 if ( !$this->mRevision ) {
00399                     wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
00400                     wfProfileOut( __METHOD__ );
00401                     return false;
00402                 }
00403             }
00404         } else {
00405             if ( !$this->mPage->getLatest() ) {
00406                 wfDebug( __METHOD__ . " failed to find page data for title " .
00407                     $this->getTitle()->getPrefixedText() . "\n" );
00408                 wfProfileOut( __METHOD__ );
00409                 return false;
00410             }
00411 
00412             $this->mRevision = $this->mPage->getRevision();
00413 
00414             if ( !$this->mRevision ) {
00415                 wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " .
00416                     $this->mPage->getLatest() . "\n" );
00417                 wfProfileOut( __METHOD__ );
00418                 return false;
00419             }
00420         }
00421 
00422         // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
00423         // We should instead work with the Revision object when we need it...
00424         // Loads if user is allowed
00425         $this->mContentObject = $this->mRevision->getContent(
00426             Revision::FOR_THIS_USER,
00427             $this->getContext()->getUser()
00428         );
00429         $this->mRevIdFetched = $this->mRevision->getId();
00430 
00431         wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
00432 
00433         wfProfileOut( __METHOD__ );
00434 
00435         return $this->mContentObject;
00436     }
00437 
00443     public function isCurrent() {
00444         # If no oldid, this is the current version.
00445         if ( $this->getOldID() == 0 ) {
00446             return true;
00447         }
00448 
00449         return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
00450     }
00451 
00459     public function getRevisionFetched() {
00460         $this->fetchContentObject();
00461 
00462         return $this->mRevision;
00463     }
00464 
00470     public function getRevIdFetched() {
00471         if ( $this->mRevIdFetched ) {
00472             return $this->mRevIdFetched;
00473         } else {
00474             return $this->mPage->getLatest();
00475         }
00476     }
00477 
00482     public function view() {
00483         global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects;
00484 
00485         wfProfileIn( __METHOD__ );
00486 
00487         # Get variables from query string
00488         # As side effect this will load the revision and update the title
00489         # in a revision ID is passed in the request, so this should remain
00490         # the first call of this method even if $oldid is used way below.
00491         $oldid = $this->getOldID();
00492 
00493         $user = $this->getContext()->getUser();
00494         # Another whitelist check in case getOldID() is altering the title
00495         $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
00496         if ( count( $permErrors ) ) {
00497             wfDebug( __METHOD__ . ": denied on secondary read check\n" );
00498             wfProfileOut( __METHOD__ );
00499             throw new PermissionsError( 'read', $permErrors );
00500         }
00501 
00502         $outputPage = $this->getContext()->getOutput();
00503         # getOldID() may as well want us to redirect somewhere else
00504         if ( $this->mRedirectUrl ) {
00505             $outputPage->redirect( $this->mRedirectUrl );
00506             wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
00507             wfProfileOut( __METHOD__ );
00508 
00509             return;
00510         }
00511 
00512         # If we got diff in the query, we want to see a diff page instead of the article.
00513         if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
00514             wfDebug( __METHOD__ . ": showing diff page\n" );
00515             $this->showDiffPage();
00516             wfProfileOut( __METHOD__ );
00517 
00518             return;
00519         }
00520 
00521         # Set page title (may be overridden by DISPLAYTITLE)
00522         $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
00523 
00524         $outputPage->setArticleFlag( true );
00525         # Allow frames by default
00526         $outputPage->allowClickjacking();
00527 
00528         $parserCache = ParserCache::singleton();
00529 
00530         $parserOptions = $this->getParserOptions();
00531         # Render printable version, use printable version cache
00532         if ( $outputPage->isPrintable() ) {
00533             $parserOptions->setIsPrintable( true );
00534             $parserOptions->setEditSection( false );
00535         } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
00536             $parserOptions->setEditSection( false );
00537         }
00538 
00539         # Try client and file cache
00540         if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
00541             if ( $wgUseETag ) {
00542                 $outputPage->setETag( $parserCache->getETag( $this, $parserOptions ) );
00543             }
00544 
00545             # Use the greatest of the page's timestamp or the timestamp of any
00546             # redirect in the chain (bug 67849)
00547             $timestamp = $this->mPage->getTouched();
00548             if ( isset( $this->mRedirectedFrom ) ) {
00549                 $timestamp = max( $timestamp, $this->mRedirectedFrom->getTouched() );
00550 
00551                 # If there can be more than one redirect in the chain, we have
00552                 # to go through the whole chain too in case an intermediate
00553                 # redirect was changed.
00554                 if ( $wgMaxRedirects > 1 ) {
00555                     $titles = Revision::newFromTitle( $this->mRedirectedFrom )
00556                         ->getContent( Revision::FOR_THIS_USER, $user )
00557                         ->getRedirectChain();
00558                     $thisTitle = $this->getTitle();
00559                     foreach ( $titles as $title ) {
00560                         if ( Title::compare( $title, $thisTitle ) === 0 ) {
00561                             break;
00562                         }
00563                         $timestamp = max( $timestamp, $title->getTouched() );
00564                     }
00565                 }
00566             }
00567 
00568             # Is it client cached?
00569             if ( $outputPage->checkLastModified( $timestamp ) ) {
00570                 wfDebug( __METHOD__ . ": done 304\n" );
00571                 wfProfileOut( __METHOD__ );
00572 
00573                 return;
00574             # Try file cache
00575             } elseif ( $wgUseFileCache && $this->tryFileCache() ) {
00576                 wfDebug( __METHOD__ . ": done file cache\n" );
00577                 # tell wgOut that output is taken care of
00578                 $outputPage->disable();
00579                 $this->mPage->doViewUpdates( $user, $oldid );
00580                 wfProfileOut( __METHOD__ );
00581 
00582                 return;
00583             }
00584         }
00585 
00586         # Should the parser cache be used?
00587         $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
00588         wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
00589         if ( $user->getStubThreshold() ) {
00590             wfIncrStats( 'pcache_miss_stub' );
00591         }
00592 
00593         $this->showRedirectedFromHeader();
00594         $this->showNamespaceHeader();
00595 
00596         # Iterate through the possible ways of constructing the output text.
00597         # Keep going until $outputDone is set, or we run out of things to do.
00598         $pass = 0;
00599         $outputDone = false;
00600         $this->mParserOutput = false;
00601 
00602         while ( !$outputDone && ++$pass ) {
00603             switch ( $pass ) {
00604                 case 1:
00605                     wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
00606                     break;
00607                 case 2:
00608                     # Early abort if the page doesn't exist
00609                     if ( !$this->mPage->exists() ) {
00610                         wfDebug( __METHOD__ . ": showing missing article\n" );
00611                         $this->showMissingArticle();
00612                         $this->mPage->doViewUpdates( $user );
00613                         wfProfileOut( __METHOD__ );
00614                         return;
00615                     }
00616 
00617                     # Try the parser cache
00618                     if ( $useParserCache ) {
00619                         $this->mParserOutput = $parserCache->get( $this, $parserOptions );
00620 
00621                         if ( $this->mParserOutput !== false ) {
00622                             if ( $oldid ) {
00623                                 wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
00624                                 $this->setOldSubtitle( $oldid );
00625                             } else {
00626                                 wfDebug( __METHOD__ . ": showing parser cache contents\n" );
00627                             }
00628                             $outputPage->addParserOutput( $this->mParserOutput );
00629                             # Ensure that UI elements requiring revision ID have
00630                             # the correct version information.
00631                             $outputPage->setRevisionId( $this->mPage->getLatest() );
00632                             # Preload timestamp to avoid a DB hit
00633                             $cachedTimestamp = $this->mParserOutput->getTimestamp();
00634                             if ( $cachedTimestamp !== null ) {
00635                                 $outputPage->setRevisionTimestamp( $cachedTimestamp );
00636                                 $this->mPage->setTimestamp( $cachedTimestamp );
00637                             }
00638                             $outputDone = true;
00639                         }
00640                     }
00641                     break;
00642                 case 3:
00643                     # This will set $this->mRevision if needed
00644                     $this->fetchContentObject();
00645 
00646                     # Are we looking at an old revision
00647                     if ( $oldid && $this->mRevision ) {
00648                         $this->setOldSubtitle( $oldid );
00649 
00650                         if ( !$this->showDeletedRevisionHeader() ) {
00651                             wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
00652                             wfProfileOut( __METHOD__ );
00653                             return;
00654                         }
00655                     }
00656 
00657                     # Ensure that UI elements requiring revision ID have
00658                     # the correct version information.
00659                     $outputPage->setRevisionId( $this->getRevIdFetched() );
00660                     # Preload timestamp to avoid a DB hit
00661                     $outputPage->setRevisionTimestamp( $this->getTimestamp() );
00662 
00663                     # Pages containing custom CSS or JavaScript get special treatment
00664                     if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
00665                         wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
00666                         $this->showCssOrJsPage();
00667                         $outputDone = true;
00668                     } elseif ( !wfRunHooks( 'ArticleContentViewCustom',
00669                             array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
00670 
00671                         # Allow extensions do their own custom view for certain pages
00672                         $outputDone = true;
00673                     } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
00674                             array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
00675 
00676                         # Allow extensions do their own custom view for certain pages
00677                         $outputDone = true;
00678                     }
00679                     break;
00680                 case 4:
00681                     # Run the parse, protected by a pool counter
00682                     wfDebug( __METHOD__ . ": doing uncached parse\n" );
00683 
00684                     $content = $this->getContentObject();
00685                     $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
00686                         $this->getRevIdFetched(), $useParserCache, $content );
00687 
00688                     if ( !$poolArticleView->execute() ) {
00689                         $error = $poolArticleView->getError();
00690                         if ( $error ) {
00691                             $outputPage->clearHTML(); // for release() errors
00692                             $outputPage->enableClientCache( false );
00693                             $outputPage->setRobotPolicy( 'noindex,nofollow' );
00694 
00695                             $errortext = $error->getWikiText( false, 'view-pool-error' );
00696                             $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
00697                         }
00698                         # Connection or timeout error
00699                         wfProfileOut( __METHOD__ );
00700                         return;
00701                     }
00702 
00703                     $this->mParserOutput = $poolArticleView->getParserOutput();
00704                     $outputPage->addParserOutput( $this->mParserOutput );
00705                     if ( $content->getRedirectTarget() ) {
00706                         $outputPage->addSubtitle(
00707                             "<span id=\"redirectsub\">" . wfMessage( 'redirectpagesub' )->parse() . "</span>"
00708                         );
00709                     }
00710 
00711                     # Don't cache a dirty ParserOutput object
00712                     if ( $poolArticleView->getIsDirty() ) {
00713                         $outputPage->setSquidMaxage( 0 );
00714                         $outputPage->addHTML( "<!-- parser cache is expired, " .
00715                             "sending anyway due to pool overload-->\n" );
00716                     }
00717 
00718                     $outputDone = true;
00719                     break;
00720                 # Should be unreachable, but just in case...
00721                 default:
00722                     break 2;
00723             }
00724         }
00725 
00726         # Get the ParserOutput actually *displayed* here.
00727         # Note that $this->mParserOutput is the *current* version output.
00728         $pOutput = ( $outputDone instanceof ParserOutput )
00729             ? $outputDone // object fetched by hook
00730             : $this->mParserOutput;
00731 
00732         # Adjust title for main page & pages with displaytitle
00733         if ( $pOutput ) {
00734             $this->adjustDisplayTitle( $pOutput );
00735         }
00736 
00737         # For the main page, overwrite the <title> element with the con-
00738         # tents of 'pagetitle-view-mainpage' instead of the default (if
00739         # that's not empty).
00740         # This message always exists because it is in the i18n files
00741         if ( $this->getTitle()->isMainPage() ) {
00742             $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
00743             if ( !$msg->isDisabled() ) {
00744                 $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
00745             }
00746         }
00747 
00748         # Check for any __NOINDEX__ tags on the page using $pOutput
00749         $policy = $this->getRobotPolicy( 'view', $pOutput );
00750         $outputPage->setIndexPolicy( $policy['index'] );
00751         $outputPage->setFollowPolicy( $policy['follow'] );
00752 
00753         $this->showViewFooter();
00754         $this->mPage->doViewUpdates( $user, $oldid );
00755 
00756         $outputPage->addModules( 'mediawiki.action.view.postEdit' );
00757 
00758         wfProfileOut( __METHOD__ );
00759     }
00760 
00765     public function adjustDisplayTitle( ParserOutput $pOutput ) {
00766         # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
00767         $titleText = $pOutput->getTitleText();
00768         if ( strval( $titleText ) !== '' ) {
00769             $this->getContext()->getOutput()->setPageTitle( $titleText );
00770         }
00771     }
00772 
00779     public function showDiffPage() {
00780         $request = $this->getContext()->getRequest();
00781         $user = $this->getContext()->getUser();
00782         $diff = $request->getVal( 'diff' );
00783         $rcid = $request->getVal( 'rcid' );
00784         $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
00785         $purge = $request->getVal( 'action' ) == 'purge';
00786         $unhide = $request->getInt( 'unhide' ) == 1;
00787         $oldid = $this->getOldID();
00788 
00789         $rev = $this->getRevisionFetched();
00790 
00791         if ( !$rev ) {
00792             $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
00793             $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 );
00794             return;
00795         }
00796 
00797         $contentHandler = $rev->getContentHandler();
00798         $de = $contentHandler->createDifferenceEngine(
00799             $this->getContext(),
00800             $oldid,
00801             $diff,
00802             $rcid,
00803             $purge,
00804             $unhide
00805         );
00806 
00807         // DifferenceEngine directly fetched the revision:
00808         $this->mRevIdFetched = $de->mNewid;
00809         $de->showDiffPage( $diffOnly );
00810 
00811         // Run view updates for the newer revision being diffed (and shown
00812         // below the diff if not $diffOnly).
00813         list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
00814         // New can be false, convert it to 0 - this conveniently means the latest revision
00815         $this->mPage->doViewUpdates( $user, (int)$new );
00816     }
00817 
00829     protected function showCssOrJsPage( $showCacheHint = true ) {
00830         $outputPage = $this->getContext()->getOutput();
00831 
00832         if ( $showCacheHint ) {
00833             $dir = $this->getContext()->getLanguage()->getDir();
00834             $lang = $this->getContext()->getLanguage()->getCode();
00835 
00836             $outputPage->wrapWikiMsg(
00837                 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
00838                 'clearyourcache'
00839             );
00840         }
00841 
00842         $this->fetchContentObject();
00843 
00844         if ( $this->mContentObject ) {
00845             // Give hooks a chance to customise the output
00846             if ( ContentHandler::runLegacyHooks(
00847                 'ShowRawCssJs',
00848                 array( $this->mContentObject, $this->getTitle(), $outputPage ) )
00849             ) {
00850                 // If no legacy hooks ran, display the content of the parser output, including RL modules,
00851                 // but excluding metadata like categories and language links
00852                 $po = $this->mContentObject->getParserOutput( $this->getTitle() );
00853                 $outputPage->addParserOutputContent( $po );
00854             }
00855         }
00856     }
00857 
00865     public function getRobotPolicy( $action, $pOutput = null ) {
00866         global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
00867 
00868         $ns = $this->getTitle()->getNamespace();
00869 
00870         # Don't index user and user talk pages for blocked users (bug 11443)
00871         if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
00872             $specificTarget = null;
00873             $vagueTarget = null;
00874             $titleText = $this->getTitle()->getText();
00875             if ( IP::isValid( $titleText ) ) {
00876                 $vagueTarget = $titleText;
00877             } else {
00878                 $specificTarget = $titleText;
00879             }
00880             if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
00881                 return array(
00882                     'index' => 'noindex',
00883                     'follow' => 'nofollow'
00884                 );
00885             }
00886         }
00887 
00888         if ( $this->mPage->getID() === 0 || $this->getOldID() ) {
00889             # Non-articles (special pages etc), and old revisions
00890             return array(
00891                 'index' => 'noindex',
00892                 'follow' => 'nofollow'
00893             );
00894         } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
00895             # Discourage indexing of printable versions, but encourage following
00896             return array(
00897                 'index' => 'noindex',
00898                 'follow' => 'follow'
00899             );
00900         } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
00901             # For ?curid=x urls, disallow indexing
00902             return array(
00903                 'index' => 'noindex',
00904                 'follow' => 'follow'
00905             );
00906         }
00907 
00908         # Otherwise, construct the policy based on the various config variables.
00909         $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
00910 
00911         if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
00912             # Honour customised robot policies for this namespace
00913             $policy = array_merge(
00914                 $policy,
00915                 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
00916             );
00917         }
00918         if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
00919             # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
00920             # a final sanity check that we have really got the parser output.
00921             $policy = array_merge(
00922                 $policy,
00923                 array( 'index' => $pOutput->getIndexPolicy() )
00924             );
00925         }
00926 
00927         if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
00928             # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
00929             $policy = array_merge(
00930                 $policy,
00931                 self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
00932             );
00933         }
00934 
00935         return $policy;
00936     }
00937 
00945     public static function formatRobotPolicy( $policy ) {
00946         if ( is_array( $policy ) ) {
00947             return $policy;
00948         } elseif ( !$policy ) {
00949             return array();
00950         }
00951 
00952         $policy = explode( ',', $policy );
00953         $policy = array_map( 'trim', $policy );
00954 
00955         $arr = array();
00956         foreach ( $policy as $var ) {
00957             if ( in_array( $var, array( 'index', 'noindex' ) ) ) {
00958                 $arr['index'] = $var;
00959             } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) {
00960                 $arr['follow'] = $var;
00961             }
00962         }
00963 
00964         return $arr;
00965     }
00966 
00974     public function showRedirectedFromHeader() {
00975         global $wgRedirectSources;
00976         $outputPage = $this->getContext()->getOutput();
00977 
00978         $request = $this->getContext()->getRequest();
00979         $rdfrom = $request->getVal( 'rdfrom' );
00980 
00981         // Construct a URL for the current page view, but with the target title
00982         $query = $request->getValues();
00983         unset( $query['rdfrom'] );
00984         unset( $query['title'] );
00985         $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
00986 
00987         if ( isset( $this->mRedirectedFrom ) ) {
00988             // This is an internally redirected page view.
00989             // We'll need a backlink to the source page for navigation.
00990             if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
00991                 $redir = Linker::linkKnown(
00992                     $this->mRedirectedFrom,
00993                     null,
00994                     array(),
00995                     array( 'redirect' => 'no' )
00996                 );
00997 
00998                 $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
00999 
01000                 // Add the script to update the displayed URL and
01001                 // set the fragment if one was specified in the redirect
01002                 $outputPage->addJsConfigVars( array(
01003                     'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
01004                 ) );
01005                 $outputPage->addModules( 'mediawiki.action.view.redirect' );
01006 
01007                 // Add a <link rel="canonical"> tag
01008                 $outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() );
01009 
01010                 // Tell the output object that the user arrived at this article through a redirect
01011                 $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
01012 
01013                 return true;
01014             }
01015         } elseif ( $rdfrom ) {
01016             // This is an externally redirected view, from some other wiki.
01017             // If it was reported from a trusted site, supply a backlink.
01018             if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
01019                 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
01020                 $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
01021 
01022                 // Add the script to update the displayed URL
01023                 $outputPage->addJsConfigVars( array(
01024                     'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
01025                 ) );
01026                 $outputPage->addModules( 'mediawiki.action.view.redirect' );
01027 
01028                 return true;
01029             }
01030         }
01031 
01032         return false;
01033     }
01034 
01039     public function showNamespaceHeader() {
01040         if ( $this->getTitle()->isTalkPage() ) {
01041             if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
01042                 $this->getContext()->getOutput()->wrapWikiMsg(
01043                     "<div class=\"mw-talkpageheader\">\n$1\n</div>",
01044                     array( 'talkpageheader' )
01045                 );
01046             }
01047         }
01048     }
01049 
01053     public function showViewFooter() {
01054         # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
01055         if ( $this->getTitle()->getNamespace() == NS_USER_TALK
01056             && IP::isValid( $this->getTitle()->getText() )
01057         ) {
01058             $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
01059         }
01060 
01061         // Show a footer allowing the user to patrol the shown revision or page if possible
01062         $patrolFooterShown = $this->showPatrolFooter();
01063 
01064         wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) );
01065     }
01066 
01076     public function showPatrolFooter() {
01077         global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
01078 
01079         $outputPage = $this->getContext()->getOutput();
01080         $user = $this->getContext()->getUser();
01081         $cache = wfGetMainCache();
01082         $rc = false;
01083 
01084         if ( !$this->getTitle()->quickUserCan( 'patrol', $user )
01085             || !( $wgUseRCPatrol || $wgUseNPPatrol )
01086         ) {
01087             // Patrolling is disabled or the user isn't allowed to
01088             return false;
01089         }
01090 
01091         wfProfileIn( __METHOD__ );
01092 
01093         // New page patrol: Get the timestamp of the oldest revison which
01094         // the revision table holds for the given page. Then we look
01095         // whether it's within the RC lifespan and if it is, we try
01096         // to get the recentchanges row belonging to that entry
01097         // (with rc_new = 1).
01098 
01099         // Check for cached results
01100         if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) {
01101             wfProfileOut( __METHOD__ );
01102             return false;
01103         }
01104 
01105         if ( $this->mRevision
01106             && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
01107         ) {
01108             // The current revision is already older than what could be in the RC table
01109             // 6h tolerance because the RC might not be cleaned out regularly
01110             wfProfileOut( __METHOD__ );
01111             return false;
01112         }
01113 
01114         $dbr = wfGetDB( DB_SLAVE );
01115         $oldestRevisionTimestamp = $dbr->selectField(
01116             'revision',
01117             'MIN( rev_timestamp )',
01118             array( 'rev_page' => $this->getTitle()->getArticleID() ),
01119             __METHOD__
01120         );
01121 
01122         if ( $oldestRevisionTimestamp
01123             && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
01124         ) {
01125             // 6h tolerance because the RC might not be cleaned out regularly
01126             $rc = RecentChange::newFromConds(
01127                 array(
01128                     'rc_new' => 1,
01129                     'rc_timestamp' => $oldestRevisionTimestamp,
01130                     'rc_namespace' => $this->getTitle()->getNamespace(),
01131                     'rc_cur_id' => $this->getTitle()->getArticleID(),
01132                     'rc_patrolled' => 0
01133                 ),
01134                 __METHOD__,
01135                 array( 'USE INDEX' => 'new_name_timestamp' )
01136             );
01137         }
01138 
01139         if ( !$rc ) {
01140             // No RC entry around
01141 
01142             // Cache the information we gathered above in case we can't patrol
01143             // Don't cache in case we can patrol as this could change
01144             $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' );
01145 
01146             wfProfileOut( __METHOD__ );
01147             return false;
01148         }
01149 
01150         if ( $rc->getPerformer()->getName() == $user->getName() ) {
01151             // Don't show a patrol link for own creations. If the user could
01152             // patrol them, they already would be patrolled
01153             wfProfileOut( __METHOD__ );
01154             return false;
01155         }
01156 
01157         $rcid = $rc->getAttribute( 'rc_id' );
01158 
01159         $token = $user->getEditToken( $rcid );
01160 
01161         $outputPage->preventClickjacking();
01162         if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
01163             $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
01164         }
01165 
01166         $link = Linker::linkKnown(
01167             $this->getTitle(),
01168             wfMessage( 'markaspatrolledtext' )->escaped(),
01169             array(),
01170             array(
01171                 'action' => 'markpatrolled',
01172                 'rcid' => $rcid,
01173                 'token' => $token,
01174             )
01175         );
01176 
01177         $outputPage->addHTML(
01178             "<div class='patrollink'>" .
01179                 wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
01180             '</div>'
01181         );
01182 
01183         wfProfileOut( __METHOD__ );
01184         return true;
01185     }
01186 
01191     public function showMissingArticle() {
01192         global $wgSend404Code;
01193 
01194         $outputPage = $this->getContext()->getOutput();
01195         // Whether the page is a root user page of an existing user (but not a subpage)
01196         $validUserPage = false;
01197 
01198         $title = $this->getTitle();
01199 
01200         # Show info in user (talk) namespace. Does the user exist? Is he blocked?
01201         if ( $title->getNamespace() == NS_USER
01202             || $title->getNamespace() == NS_USER_TALK
01203         ) {
01204             $parts = explode( '/', $title->getText() );
01205             $rootPart = $parts[0];
01206             $user = User::newFromName( $rootPart, false /* allow IP users*/ );
01207             $ip = User::isIP( $rootPart );
01208             $block = Block::newFromTarget( $user, $user );
01209 
01210             if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
01211                 $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
01212                     array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) );
01213             } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked
01214                 LogEventsList::showLogExtract(
01215                     $outputPage,
01216                     'block',
01217                     MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
01218                     '',
01219                     array(
01220                         'lim' => 1,
01221                         'showIfEmpty' => false,
01222                         'msgKey' => array(
01223                             'blocked-notice-logextract',
01224                             $user->getName() # Support GENDER in notice
01225                         )
01226                     )
01227                 );
01228                 $validUserPage = !$title->isSubpage();
01229             } else {
01230                 $validUserPage = !$title->isSubpage();
01231             }
01232         }
01233 
01234         wfRunHooks( 'ShowMissingArticle', array( $this ) );
01235 
01236         // Give extensions a chance to hide their (unrelated) log entries
01237         $logTypes = array( 'delete', 'move' );
01238         $conds = array( "log_action != 'revision'" );
01239         wfRunHooks( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) );
01240 
01241         # Show delete and move logs
01242         $member = $title->getNamespace() . ':' . $title->getDBkey();
01243         // @todo: move optimization to showLogExtract()?
01244         if ( BloomCache::get( 'main' )->check( wfWikiId(), 'TitleHasLogs', $member ) ) {
01245             LogEventsList::showLogExtract( $outputPage, $logTypes, $title, '',
01246                 array( 'lim' => 10,
01247                     'conds' => $conds,
01248                     'showIfEmpty' => false,
01249                     'msgKey' => array( 'moveddeleted-notice' ) )
01250             );
01251         }
01252 
01253         if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
01254             // If there's no backing content, send a 404 Not Found
01255             // for better machine handling of broken links.
01256             $this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" );
01257         }
01258 
01259         // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
01260         $policy = $this->getRobotPolicy( 'view' );
01261         $outputPage->setIndexPolicy( $policy['index'] );
01262         $outputPage->setFollowPolicy( $policy['follow'] );
01263 
01264         $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
01265 
01266         if ( !$hookResult ) {
01267             return;
01268         }
01269 
01270         # Show error message
01271         $oldid = $this->getOldID();
01272         if ( $oldid ) {
01273             $text = wfMessage( 'missing-revision', $oldid )->plain();
01274         } elseif ( $title->getNamespace() === NS_MEDIAWIKI ) {
01275             // Use the default message text
01276             $text = $title->getDefaultMessageText();
01277         } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
01278             && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
01279         ) {
01280             $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
01281             $text = wfMessage( $message )->plain();
01282         } else {
01283             $text = wfMessage( 'noarticletext-nopermission' )->plain();
01284         }
01285         $text = "<div class='noarticletext'>\n$text\n</div>";
01286 
01287         $outputPage->addWikiText( $text );
01288     }
01289 
01296     public function showDeletedRevisionHeader() {
01297         if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
01298             // Not deleted
01299             return true;
01300         }
01301 
01302         $outputPage = $this->getContext()->getOutput();
01303         $user = $this->getContext()->getUser();
01304         // If the user is not allowed to see it...
01305         if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
01306             $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
01307                 'rev-deleted-text-permission' );
01308 
01309             return false;
01310         // If the user needs to confirm that they want to see it...
01311         } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
01312             # Give explanation and add a link to view the revision...
01313             $oldid = intval( $this->getOldID() );
01314             $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
01315             $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
01316                 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
01317             $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
01318                 array( $msg, $link ) );
01319 
01320             return false;
01321         // We are allowed to see...
01322         } else {
01323             $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
01324                 'rev-suppressed-text-view' : 'rev-deleted-text-view';
01325             $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
01326 
01327             return true;
01328         }
01329     }
01330 
01339     public function setOldSubtitle( $oldid = 0 ) {
01340         if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
01341             return;
01342         }
01343 
01344         $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1;
01345 
01346         # Cascade unhide param in links for easy deletion browsing
01347         $extraParams = array();
01348         if ( $unhide ) {
01349             $extraParams['unhide'] = 1;
01350         }
01351 
01352         if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
01353             $revision = $this->mRevision;
01354         } else {
01355             $revision = Revision::newFromId( $oldid );
01356         }
01357 
01358         $timestamp = $revision->getTimestamp();
01359 
01360         $current = ( $oldid == $this->mPage->getLatest() );
01361         $language = $this->getContext()->getLanguage();
01362         $user = $this->getContext()->getUser();
01363 
01364         $td = $language->userTimeAndDate( $timestamp, $user );
01365         $tddate = $language->userDate( $timestamp, $user );
01366         $tdtime = $language->userTime( $timestamp, $user );
01367 
01368         # Show user links if allowed to see them. If hidden, then show them only if requested...
01369         $userlinks = Linker::revUserTools( $revision, !$unhide );
01370 
01371         $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
01372             ? 'revision-info-current'
01373             : 'revision-info';
01374 
01375         $outputPage = $this->getContext()->getOutput();
01376         $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
01377             $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
01378             $tdtime, $revision->getUserText() )->rawParams( Linker::revComment( $revision, true, true ) )->parse() . "</div>" );
01379 
01380         $lnk = $current
01381             ? wfMessage( 'currentrevisionlink' )->escaped()
01382             : Linker::linkKnown(
01383                 $this->getTitle(),
01384                 wfMessage( 'currentrevisionlink' )->escaped(),
01385                 array(),
01386                 $extraParams
01387             );
01388         $curdiff = $current
01389             ? wfMessage( 'diff' )->escaped()
01390             : Linker::linkKnown(
01391                 $this->getTitle(),
01392                 wfMessage( 'diff' )->escaped(),
01393                 array(),
01394                 array(
01395                     'diff' => 'cur',
01396                     'oldid' => $oldid
01397                 ) + $extraParams
01398             );
01399         $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
01400         $prevlink = $prev
01401             ? Linker::linkKnown(
01402                 $this->getTitle(),
01403                 wfMessage( 'previousrevision' )->escaped(),
01404                 array(),
01405                 array(
01406                     'direction' => 'prev',
01407                     'oldid' => $oldid
01408                 ) + $extraParams
01409             )
01410             : wfMessage( 'previousrevision' )->escaped();
01411         $prevdiff = $prev
01412             ? Linker::linkKnown(
01413                 $this->getTitle(),
01414                 wfMessage( 'diff' )->escaped(),
01415                 array(),
01416                 array(
01417                     'diff' => 'prev',
01418                     'oldid' => $oldid
01419                 ) + $extraParams
01420             )
01421             : wfMessage( 'diff' )->escaped();
01422         $nextlink = $current
01423             ? wfMessage( 'nextrevision' )->escaped()
01424             : Linker::linkKnown(
01425                 $this->getTitle(),
01426                 wfMessage( 'nextrevision' )->escaped(),
01427                 array(),
01428                 array(
01429                     'direction' => 'next',
01430                     'oldid' => $oldid
01431                 ) + $extraParams
01432             );
01433         $nextdiff = $current
01434             ? wfMessage( 'diff' )->escaped()
01435             : Linker::linkKnown(
01436                 $this->getTitle(),
01437                 wfMessage( 'diff' )->escaped(),
01438                 array(),
01439                 array(
01440                     'diff' => 'next',
01441                     'oldid' => $oldid
01442                 ) + $extraParams
01443             );
01444 
01445         $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
01446         if ( $cdel !== '' ) {
01447             $cdel .= ' ';
01448         }
01449 
01450         $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
01451             wfMessage( 'revision-nav' )->rawParams(
01452                 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
01453             )->escaped() . "</div>" );
01454     }
01455 
01467     public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
01468         $lang = $this->getTitle()->getPageLanguage();
01469         $out = $this->getContext()->getOutput();
01470         if ( $appendSubtitle ) {
01471             $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
01472         }
01473         $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
01474         return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
01475     }
01476 
01489     public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
01490         if ( !is_array( $target ) ) {
01491             $target = array( $target );
01492         }
01493 
01494         $html = '<ul class="redirectText">';
01496         foreach ( $target as $title ) {
01497             $html .= '<li>' . Linker::link(
01498                 $title,
01499                 htmlspecialchars( $title->getFullText() ),
01500                 array(),
01501                 // Automatically append redirect=no to each link, since most of them are
01502                 // redirect pages themselves.
01503                 array( 'redirect' => 'no' ),
01504                 ( $forceKnown ? array( 'known', 'noclasses' ) : array() )
01505             ) . '</li>';
01506         }
01507 
01508         $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->text();
01509 
01510         return '<div class="redirectMsg">' .
01511             '<p>' . $redirectToText . '</p>' .
01512             $html .
01513             '</div>';
01514     }
01515 
01519     public function render() {
01520         $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
01521         $this->getContext()->getOutput()->setArticleBodyOnly( true );
01522         $this->getContext()->getOutput()->enableSectionEditLinks( false );
01523         $this->view();
01524     }
01525 
01529     public function protect() {
01530         $form = new ProtectionForm( $this );
01531         $form->execute();
01532     }
01533 
01537     public function unprotect() {
01538         $this->protect();
01539     }
01540 
01544     public function delete() {
01545         # This code desperately needs to be totally rewritten
01546 
01547         $title = $this->getTitle();
01548         $user = $this->getContext()->getUser();
01549 
01550         # Check permissions
01551         $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
01552         if ( count( $permissionErrors ) ) {
01553             throw new PermissionsError( 'delete', $permissionErrors );
01554         }
01555 
01556         # Read-only check...
01557         if ( wfReadOnly() ) {
01558             throw new ReadOnlyError;
01559         }
01560 
01561         # Better double-check that it hasn't been deleted yet!
01562         $this->mPage->loadPageData( 'fromdbmaster' );
01563         if ( !$this->mPage->exists() ) {
01564             $deleteLogPage = new LogPage( 'delete' );
01565             $outputPage = $this->getContext()->getOutput();
01566             $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) );
01567             $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
01568                     array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) )
01569                 );
01570             $outputPage->addHTML(
01571                 Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
01572             );
01573             LogEventsList::showLogExtract(
01574                 $outputPage,
01575                 'delete',
01576                 $title
01577             );
01578 
01579             return;
01580         }
01581 
01582         $request = $this->getContext()->getRequest();
01583         $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
01584         $deleteReason = $request->getText( 'wpReason' );
01585 
01586         if ( $deleteReasonList == 'other' ) {
01587             $reason = $deleteReason;
01588         } elseif ( $deleteReason != '' ) {
01589             // Entry from drop down menu + additional comment
01590             $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
01591             $reason = $deleteReasonList . $colonseparator . $deleteReason;
01592         } else {
01593             $reason = $deleteReasonList;
01594         }
01595 
01596         if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
01597             array( 'delete', $this->getTitle()->getPrefixedText() ) )
01598         ) {
01599             # Flag to hide all contents of the archived revisions
01600             $suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
01601 
01602             $this->doDelete( $reason, $suppress );
01603 
01604             WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
01605 
01606             return;
01607         }
01608 
01609         // Generate deletion reason
01610         $hasHistory = false;
01611         if ( !$reason ) {
01612             try {
01613                 $reason = $this->generateReason( $hasHistory );
01614             } catch ( MWException $e ) {
01615                 # if a page is horribly broken, we still want to be able to
01616                 # delete it. So be lenient about errors here.
01617                 wfDebug( "Error while building auto delete summary: $e" );
01618                 $reason = '';
01619             }
01620         }
01621 
01622         // If the page has a history, insert a warning
01623         if ( $hasHistory ) {
01624             $title = $this->getTitle();
01625 
01626             // The following can use the real revision count as this is only being shown for users that can delete
01627             // this page.
01628             // This, as a side-effect, also makes sure that the following query isn't being run for pages with a
01629             // larger history, unless the user has the 'bigdelete' right (and is about to delete this page).
01630             $dbr = wfGetDB( DB_SLAVE );
01631             $revisions = $edits = (int)$dbr->selectField(
01632                 'revision',
01633                 'COUNT(rev_page)',
01634                 array( 'rev_page' => $title->getArticleID() ),
01635                 __METHOD__
01636             );
01637 
01638             // @todo FIXME: i18n issue/patchwork message
01639             $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' .
01640                 wfMessage( 'historywarning' )->numParams( $revisions )->parse() .
01641                 wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title,
01642                     wfMessage( 'history' )->escaped(),
01643                     array( 'rel' => 'archives' ),
01644                     array( 'action' => 'history' ) ) .
01645                 '</strong>'
01646             );
01647 
01648             if ( $title->isBigDeletion() ) {
01649                 global $wgDeleteRevisionsLimit;
01650                 $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
01651                     array(
01652                         'delete-warning-toobig',
01653                         $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
01654                     )
01655                 );
01656             }
01657         }
01658 
01659         $this->confirmDelete( $reason );
01660     }
01661 
01667     public function confirmDelete( $reason ) {
01668         wfDebug( "Article::confirmDelete\n" );
01669 
01670         $title = $this->getTitle();
01671         $outputPage = $this->getContext()->getOutput();
01672         $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
01673         $outputPage->addBacklinkSubtitle( $title );
01674         $outputPage->setRobotPolicy( 'noindex,nofollow' );
01675         $backlinkCache = $title->getBacklinkCache();
01676         if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
01677             $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
01678                 'deleting-backlinks-warning' );
01679         }
01680         $outputPage->addWikiMsg( 'confirmdeletetext' );
01681 
01682         wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) );
01683 
01684         $user = $this->getContext()->getUser();
01685 
01686         if ( $user->isAllowed( 'suppressrevision' ) ) {
01687             $suppress = "<tr id=\"wpDeleteSuppressRow\">
01688                     <td></td>
01689                     <td class='mw-input'><strong>" .
01690                         Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
01691                             'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
01692                     "</strong></td>
01693                 </tr>";
01694         } else {
01695             $suppress = '';
01696         }
01697         $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
01698 
01699         $form = Xml::openElement( 'form', array( 'method' => 'post',
01700             'action' => $title->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
01701             Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
01702             Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) .
01703             Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
01704             "<tr id=\"wpDeleteReasonListRow\">
01705                 <td class='mw-label'>" .
01706                     Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
01707                 "</td>
01708                 <td class='mw-input'>" .
01709                     Xml::listDropDown(
01710                         'wpDeleteReasonList',
01711                         wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
01712                         wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
01713                         '',
01714                         'wpReasonDropDown',
01715                         1
01716                     ) .
01717                 "</td>
01718             </tr>
01719             <tr id=\"wpDeleteReasonRow\">
01720                 <td class='mw-label'>" .
01721                     Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
01722                 "</td>
01723                 <td class='mw-input'>" .
01724                 Html::input( 'wpReason', $reason, 'text', array(
01725                     'size' => '60',
01726                     'maxlength' => '255',
01727                     'tabindex' => '2',
01728                     'id' => 'wpReason',
01729                     'autofocus'
01730                 ) ) .
01731                 "</td>
01732             </tr>";
01733 
01734         # Disallow watching if user is not logged in
01735         if ( $user->isLoggedIn() ) {
01736             $form .= "
01737             <tr>
01738                 <td></td>
01739                 <td class='mw-input'>" .
01740                     Xml::checkLabel( wfMessage( 'watchthis' )->text(),
01741                         'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
01742                 "</td>
01743             </tr>";
01744         }
01745 
01746         $form .= "
01747             $suppress
01748             <tr>
01749                 <td></td>
01750                 <td class='mw-submit'>" .
01751                     Xml::submitButton( wfMessage( 'deletepage' )->text(),
01752                         array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
01753                 "</td>
01754             </tr>" .
01755             Xml::closeElement( 'table' ) .
01756             Xml::closeElement( 'fieldset' ) .
01757             Html::hidden(
01758                 'wpEditToken',
01759                 $user->getEditToken( array( 'delete', $title->getPrefixedText() ) )
01760             ) .
01761             Xml::closeElement( 'form' );
01762 
01763             if ( $user->isAllowed( 'editinterface' ) ) {
01764                 $dropdownTitle = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
01765                 $link = Linker::link(
01766                     $dropdownTitle,
01767                     wfMessage( 'delete-edit-reasonlist' )->escaped(),
01768                     array(),
01769                     array( 'action' => 'edit' )
01770                 );
01771                 $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
01772             }
01773 
01774         $outputPage->addHTML( $form );
01775 
01776         $deleteLogPage = new LogPage( 'delete' );
01777         $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
01778         LogEventsList::showLogExtract( $outputPage, 'delete', $title );
01779     }
01780 
01786     public function doDelete( $reason, $suppress = false ) {
01787         $error = '';
01788         $outputPage = $this->getContext()->getOutput();
01789         $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error );
01790 
01791         if ( $status->isGood() ) {
01792             $deleted = $this->getTitle()->getPrefixedText();
01793 
01794             $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
01795             $outputPage->setRobotPolicy( 'noindex,nofollow' );
01796 
01797             $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
01798 
01799             $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
01800             $outputPage->returnToMain( false );
01801         } else {
01802             $outputPage->setPageTitle(
01803                 wfMessage( 'cannotdelete-title',
01804                     $this->getTitle()->getPrefixedText() )
01805             );
01806 
01807             if ( $error == '' ) {
01808                 $outputPage->addWikiText(
01809                     "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
01810                 );
01811                 $deleteLogPage = new LogPage( 'delete' );
01812                 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
01813 
01814                 LogEventsList::showLogExtract(
01815                     $outputPage,
01816                     'delete',
01817                     $this->getTitle()
01818                 );
01819             } else {
01820                 $outputPage->addHTML( $error );
01821             }
01822         }
01823     }
01824 
01825     /* Caching functions */
01826 
01834     protected function tryFileCache() {
01835         static $called = false;
01836 
01837         if ( $called ) {
01838             wfDebug( "Article::tryFileCache(): called twice!?\n" );
01839             return false;
01840         }
01841 
01842         $called = true;
01843         if ( $this->isFileCacheable() ) {
01844             $cache = new HTMLFileCache( $this->getTitle(), 'view' );
01845             if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
01846                 wfDebug( "Article::tryFileCache(): about to load file\n" );
01847                 $cache->loadFromFileCache( $this->getContext() );
01848                 return true;
01849             } else {
01850                 wfDebug( "Article::tryFileCache(): starting buffer\n" );
01851                 ob_start( array( &$cache, 'saveToFileCache' ) );
01852             }
01853         } else {
01854             wfDebug( "Article::tryFileCache(): not cacheable\n" );
01855         }
01856 
01857         return false;
01858     }
01859 
01864     public function isFileCacheable() {
01865         $cacheable = false;
01866 
01867         if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
01868             $cacheable = $this->mPage->getID()
01869                 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
01870             // Extension may have reason to disable file caching on some pages.
01871             if ( $cacheable ) {
01872                 $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
01873             }
01874         }
01875 
01876         return $cacheable;
01877     }
01878 
01892     public function getParserOutput( $oldid = null, User $user = null ) {
01893         //XXX: bypasses mParserOptions and thus setParserOptions()
01894 
01895         if ( $user === null ) {
01896             $parserOptions = $this->getParserOptions();
01897         } else {
01898             $parserOptions = $this->mPage->makeParserOptions( $user );
01899         }
01900 
01901         return $this->mPage->getParserOutput( $parserOptions, $oldid );
01902     }
01903 
01910     public function setParserOptions( ParserOptions $options ) {
01911         if ( $this->mParserOptions ) {
01912             throw new MWException( "can't change parser options after they have already been set" );
01913         }
01914 
01915         // clone, so if $options is modified later, it doesn't confuse the parser cache.
01916         $this->mParserOptions = clone $options;
01917     }
01918 
01923     public function getParserOptions() {
01924         if ( !$this->mParserOptions ) {
01925             $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
01926         }
01927         // Clone to allow modifications of the return value without affecting cache
01928         return clone $this->mParserOptions;
01929     }
01930 
01937     public function setContext( $context ) {
01938         $this->mContext = $context;
01939     }
01940 
01947     public function getContext() {
01948         if ( $this->mContext instanceof IContextSource ) {
01949             return $this->mContext;
01950         } else {
01951             wfDebug( __METHOD__ . " called and \$mContext is null. " .
01952                 "Return RequestContext::getMain(); for sanity\n" );
01953             return RequestContext::getMain();
01954         }
01955     }
01956 
01964     public function __get( $fname ) {
01965         if ( property_exists( $this->mPage, $fname ) ) {
01966             #wfWarn( "Access to raw $fname field " . __CLASS__ );
01967             return $this->mPage->$fname;
01968         }
01969         trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
01970     }
01971 
01979     public function __set( $fname, $fvalue ) {
01980         if ( property_exists( $this->mPage, $fname ) ) {
01981             #wfWarn( "Access to raw $fname field of " . __CLASS__ );
01982             $this->mPage->$fname = $fvalue;
01983         // Note: extensions may want to toss on new fields
01984         } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
01985             $this->mPage->$fname = $fvalue;
01986         } else {
01987             trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
01988         }
01989     }
01990 
01999     public function __call( $fname, $args ) {
02000         if ( is_callable( array( $this->mPage, $fname ) ) ) {
02001             #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" );
02002             return call_user_func_array( array( $this->mPage, $fname ), $args );
02003         }
02004         trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
02005     }
02006 
02007     // ****** B/C functions to work-around PHP silliness with __call and references ****** //
02008 
02017     public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
02018         $reason, User $user
02019     ) {
02020         return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
02021     }
02022 
02030     public function updateRestrictions( $limit = array(), $reason = '',
02031         &$cascade = 0, $expiry = array()
02032     ) {
02033         return $this->mPage->doUpdateRestrictions(
02034             $limit,
02035             $expiry,
02036             $cascade,
02037             $reason,
02038             $this->getContext()->getUser()
02039         );
02040     }
02041 
02050     public function doDeleteArticle( $reason, $suppress = false, $id = 0,
02051         $commit = true, &$error = ''
02052     ) {
02053         return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
02054     }
02055 
02065     public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
02066         $user = is_null( $user ) ? $this->getContext()->getUser() : $user;
02067         return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
02068     }
02069 
02078     public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
02079         $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser;
02080         return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
02081     }
02082 
02087     public function generateReason( &$hasHistory ) {
02088         $title = $this->mPage->getTitle();
02089         $handler = ContentHandler::getForTitle( $title );
02090         return $handler->getAutoDeleteReason( $title, $hasHistory );
02091     }
02092 
02093     // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
02094 
02100     public static function selectFields() {
02101         wfDeprecated( __METHOD__, '1.24' );
02102         return WikiPage::selectFields();
02103     }
02104 
02110     public static function onArticleCreate( $title ) {
02111         wfDeprecated( __METHOD__, '1.24' );
02112         WikiPage::onArticleCreate( $title );
02113     }
02114 
02120     public static function onArticleDelete( $title ) {
02121         wfDeprecated( __METHOD__, '1.24' );
02122         WikiPage::onArticleDelete( $title );
02123     }
02124 
02130     public static function onArticleEdit( $title ) {
02131         wfDeprecated( __METHOD__, '1.24' );
02132         WikiPage::onArticleEdit( $title );
02133     }
02134 
02142     public static function getAutosummary( $oldtext, $newtext, $flags ) {
02143         return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
02144     }
02145     // ******
02146 }