MediaWiki  REL1_19
Article.php
Go to the documentation of this file.
00001 <?php
00020 class Article extends Page {
00028         protected $mContext;
00029 
00033         protected $mPage;
00034 
00038         public $mParserOptions;
00039 
00040         var $mContent;                    // !<
00041         var $mContentLoaded = false;      // !<
00042         var $mOldId;                      // !<
00043 
00047         var $mRedirectedFrom = null;
00048 
00052         var $mRedirectUrl = false;        // !<
00053         var $mRevIdFetched = 0;           // !<
00054 
00058         var $mRevision = null;
00059 
00063         var $mParserOutput;
00064 
00072         public function __construct( Title $title, $oldId = null ) {
00073                 $this->mOldId = $oldId;
00074                 $this->mPage = $this->newPage( $title );
00075         }
00076 
00081         protected function newPage( Title $title ) {
00082                 return new WikiPage( $title );
00083         }
00084 
00090         public static function newFromID( $id ) {
00091                 $t = Title::newFromID( $id );
00092                 # @todo FIXME: Doesn't inherit right
00093                 return $t == null ? null : new self( $t );
00094                 # return $t == null ? null : new static( $t ); // PHP 5.3
00095         }
00096 
00104         public static function newFromTitle( $title, IContextSource $context ) {
00105                 if ( NS_MEDIA == $title->getNamespace() ) {
00106                         // FIXME: where should this go?
00107                         $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
00108                 }
00109 
00110                 $page = null;
00111                 wfRunHooks( 'ArticleFromTitle', array( &$title, &$page ) );
00112                 if ( !$page ) {
00113                         switch( $title->getNamespace() ) {
00114                                 case NS_FILE:
00115                                         $page = new ImagePage( $title );
00116                                         break;
00117                                 case NS_CATEGORY:
00118                                         $page = new CategoryPage( $title );
00119                                         break;
00120                                 default:
00121                                         $page = new Article( $title );
00122                         }
00123                 }
00124                 $page->setContext( $context );
00125 
00126                 return $page;
00127         }
00128 
00136         public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
00137                 $article = self::newFromTitle( $page->getTitle(), $context );
00138                 $article->mPage = $page; // override to keep process cached vars
00139                 return $article;
00140         }
00141 
00147         public function setRedirectedFrom( Title $from ) {
00148                 $this->mRedirectedFrom = $from;
00149         }
00150 
00156         public function getTitle() {
00157                 return $this->mPage->getTitle();
00158         }
00159 
00166         public function getPage() {
00167                 return $this->mPage;
00168         }
00169 
00173         public function clear() {
00174                 $this->mContentLoaded = false;
00175 
00176                 $this->mRedirectedFrom = null; # Title object if set
00177                 $this->mRevIdFetched = 0;
00178                 $this->mRedirectUrl = false;
00179 
00180                 $this->mPage->clear();
00181         }
00182 
00193         public function getContent() {
00194                 global $wgUser;
00195 
00196                 wfProfileIn( __METHOD__ );
00197 
00198                 if ( $this->mPage->getID() === 0 ) {
00199                         # If this is a MediaWiki:x message, then load the messages
00200                         # and return the message value for x.
00201                         if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
00202                                 $text = $this->getTitle()->getDefaultMessageText();
00203                                 if ( $text === false ) {
00204                                         $text = '';
00205                                 }
00206                         } else {
00207                                 $text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' );
00208                         }
00209                         wfProfileOut( __METHOD__ );
00210 
00211                         return $text;
00212                 } else {
00213                         $this->fetchContent();
00214                         wfProfileOut( __METHOD__ );
00215 
00216                         return $this->mContent;
00217                 }
00218         }
00219 
00224         public function getOldID() {
00225                 if ( is_null( $this->mOldId ) ) {
00226                         $this->mOldId = $this->getOldIDFromRequest();
00227                 }
00228 
00229                 return $this->mOldId;
00230         }
00231 
00237         public function getOldIDFromRequest() {
00238                 global $wgRequest;
00239 
00240                 $this->mRedirectUrl = false;
00241 
00242                 $oldid = $wgRequest->getIntOrNull( 'oldid' );
00243 
00244                 if ( $oldid === null ) {
00245                         return 0;
00246                 }
00247 
00248                 if ( $oldid !== 0 ) {
00249                         # Load the given revision and check whether the page is another one.
00250                         # In that case, update this instance to reflect the change.
00251                         $this->mRevision = Revision::newFromId( $oldid );
00252                         if ( $this->mRevision !== null ) {
00253                                 // Revision title doesn't match the page title given?
00254                                 if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
00255                                         $function = array( get_class( $this->mPage ), 'newFromID' );
00256                                         $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
00257                                 }
00258                         }
00259                 }
00260 
00261                 if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
00262                         $nextid = $this->getTitle()->getNextRevisionID( $oldid );
00263                         if ( $nextid ) {
00264                                 $oldid = $nextid;
00265                                 $this->mRevision = null;
00266                         } else {
00267                                 $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
00268                         }
00269                 } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
00270                         $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
00271                         if ( $previd ) {
00272                                 $oldid = $previd;
00273                                 $this->mRevision = null;
00274                         }
00275                 }
00276 
00277                 return $oldid;
00278         }
00279 
00285         function loadContent() {
00286                 wfDeprecated( __METHOD__, '1.19' );
00287                 $this->fetchContent();
00288         }
00289 
00296         function fetchContent() {
00297                 if ( $this->mContentLoaded ) {
00298                         return $this->mContent;
00299                 }
00300 
00301                 wfProfileIn( __METHOD__ );
00302 
00303                 $this->mContentLoaded = true;
00304 
00305                 $oldid = $this->getOldID();
00306 
00307                 # Pre-fill content with error message so that if something
00308                 # fails we'll have something telling us what we intended.
00309                 $t = $this->getTitle()->getPrefixedText();
00310                 $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
00311                 $this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
00312 
00313                 if ( $oldid ) {
00314                         # $this->mRevision might already be fetched by getOldIDFromRequest()
00315                         if ( !$this->mRevision ) {
00316                                 $this->mRevision = Revision::newFromId( $oldid );
00317                                 if ( !$this->mRevision ) {
00318                                         wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
00319                                         wfProfileOut( __METHOD__ );
00320                                         return false;
00321                                 }
00322                         }
00323                 } else {
00324                         if ( !$this->mPage->getLatest() ) {
00325                                 wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
00326                                 wfProfileOut( __METHOD__ );
00327                                 return false;
00328                         }
00329 
00330                         $this->mRevision = $this->mPage->getRevision();
00331                         if ( !$this->mRevision ) {
00332                                 wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
00333                                 wfProfileOut( __METHOD__ );
00334                                 return false;
00335                         }
00336                 }
00337 
00338                 // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
00339                 // We should instead work with the Revision object when we need it...
00340                 $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
00341                 $this->mRevIdFetched = $this->mRevision->getId();
00342 
00343                 wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
00344 
00345                 wfProfileOut( __METHOD__ );
00346 
00347                 return $this->mContent;
00348         }
00349 
00354         public function forUpdate() {
00355                 wfDeprecated( __METHOD__, '1.18' );
00356         }
00357 
00363         public function isCurrent() {
00364                 # If no oldid, this is the current version.
00365                 if ( $this->getOldID() == 0 ) {
00366                         return true;
00367                 }
00368 
00369                 return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
00370         }
00371 
00379         public function getRevisionFetched() {
00380                 $this->fetchContent();
00381 
00382                 return $this->mRevision;
00383         }
00384 
00390         public function getRevIdFetched() {
00391                 if ( $this->mRevIdFetched ) {
00392                         return $this->mRevIdFetched;
00393                 } else {
00394                         return $this->mPage->getLatest();
00395                 }
00396         }
00397 
00402         public function view() {
00403                 global $wgUser, $wgOut, $wgRequest, $wgParser;
00404                 global $wgUseFileCache, $wgUseETag, $wgDebugToolbar;
00405 
00406                 wfProfileIn( __METHOD__ );
00407 
00408                 # Get variables from query string
00409                 # As side effect this will load the revision and update the title
00410                 # in a revision ID is passed in the request, so this should remain
00411                 # the first call of this method even if $oldid is used way below.
00412                 $oldid = $this->getOldID();
00413 
00414                 # Another whitelist check in case getOldID() is altering the title
00415                 $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $wgUser );
00416                 if ( count( $permErrors ) ) {
00417                         wfDebug( __METHOD__ . ": denied on secondary read check\n" );
00418                         wfProfileOut( __METHOD__ );
00419                         throw new PermissionsError( 'read', $permErrors );
00420                 }
00421 
00422                 # getOldID() may as well want us to redirect somewhere else
00423                 if ( $this->mRedirectUrl ) {
00424                         $wgOut->redirect( $this->mRedirectUrl );
00425                         wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
00426                         wfProfileOut( __METHOD__ );
00427 
00428                         return;
00429                 }
00430 
00431                 # If we got diff in the query, we want to see a diff page instead of the article.
00432                 if ( $wgRequest->getCheck( 'diff' ) ) {
00433                         wfDebug( __METHOD__ . ": showing diff page\n" );
00434                         $this->showDiffPage();
00435                         wfProfileOut( __METHOD__ );
00436 
00437                         return;
00438                 }
00439 
00440                 # Set page title (may be overridden by DISPLAYTITLE)
00441                 $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
00442 
00443                 $wgOut->setArticleFlag( true );
00444                 # Allow frames by default
00445                 $wgOut->allowClickjacking();
00446 
00447                 $parserCache = ParserCache::singleton();
00448 
00449                 $parserOptions = $this->getParserOptions();
00450                 # Render printable version, use printable version cache
00451                 if ( $wgOut->isPrintable() ) {
00452                         $parserOptions->setIsPrintable( true );
00453                         $parserOptions->setEditSection( false );
00454                 } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
00455                         $parserOptions->setEditSection( false );
00456                 }
00457 
00458                 # Try client and file cache
00459                 if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
00460                         if ( $wgUseETag ) {
00461                                 $wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) );
00462                         }
00463 
00464                         # Is it client cached?
00465                         if ( $wgOut->checkLastModified( $this->mPage->getTouched() ) ) {
00466                                 wfDebug( __METHOD__ . ": done 304\n" );
00467                                 wfProfileOut( __METHOD__ );
00468 
00469                                 return;
00470                         # Try file cache
00471                         } elseif ( $wgUseFileCache && $this->tryFileCache() ) {
00472                                 wfDebug( __METHOD__ . ": done file cache\n" );
00473                                 # tell wgOut that output is taken care of
00474                                 $wgOut->disable();
00475                                 $this->mPage->doViewUpdates( $wgUser );
00476                                 wfProfileOut( __METHOD__ );
00477 
00478                                 return;
00479                         }
00480                 }
00481 
00482                 # Should the parser cache be used?
00483                 $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
00484                 wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
00485                 if ( $wgUser->getStubThreshold() ) {
00486                         wfIncrStats( 'pcache_miss_stub' );
00487                 }
00488 
00489                 $this->showRedirectedFromHeader();
00490                 $this->showNamespaceHeader();
00491 
00492                 # Iterate through the possible ways of constructing the output text.
00493                 # Keep going until $outputDone is set, or we run out of things to do.
00494                 $pass = 0;
00495                 $outputDone = false;
00496                 $this->mParserOutput = false;
00497 
00498                 while ( !$outputDone && ++$pass ) {
00499                         switch( $pass ) {
00500                                 case 1:
00501                                         wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
00502                                         break;
00503                                 case 2:
00504                                         # Early abort if the page doesn't exist
00505                                         if ( !$this->mPage->exists() ) {
00506                                                 wfDebug( __METHOD__ . ": showing missing article\n" );
00507                                                 $this->showMissingArticle();
00508                                                 wfProfileOut( __METHOD__ );
00509                                                 return;
00510                                         }
00511 
00512                                         # Try the parser cache
00513                                         if ( $useParserCache ) {
00514                                                 $this->mParserOutput = $parserCache->get( $this, $parserOptions );
00515 
00516                                                 if ( $this->mParserOutput !== false ) {
00517                                                         if ( $oldid ) {
00518                                                                 wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
00519                                                                 $this->setOldSubtitle( $oldid );
00520                                                         } else {
00521                                                                 wfDebug( __METHOD__ . ": showing parser cache contents\n" );
00522                                                         }
00523                                                         $wgOut->addParserOutput( $this->mParserOutput );
00524                                                         # Ensure that UI elements requiring revision ID have
00525                                                         # the correct version information.
00526                                                         $wgOut->setRevisionId( $this->mPage->getLatest() );
00527                                                         # Preload timestamp to avoid a DB hit
00528                                                         $cachedTimestamp = $this->mParserOutput->getTimestamp();
00529                                                         if ( $cachedTimestamp !== null ) {
00530                                                                 $wgOut->setRevisionTimestamp( $cachedTimestamp );
00531                                                                 $this->mPage->setTimestamp( $cachedTimestamp );
00532                                                         }
00533                                                         $outputDone = true;
00534                                                 }
00535                                         }
00536                                         break;
00537                                 case 3:
00538                                         # This will set $this->mRevision if needed
00539                                         $this->fetchContent();
00540 
00541                                         # Are we looking at an old revision
00542                                         if ( $oldid && $this->mRevision ) {
00543                                                 $this->setOldSubtitle( $oldid );
00544 
00545                                                 if ( !$this->showDeletedRevisionHeader() ) {
00546                                                         wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
00547                                                         wfProfileOut( __METHOD__ );
00548                                                         return;
00549                                                 }
00550                                         }
00551 
00552                                         # Ensure that UI elements requiring revision ID have
00553                                         # the correct version information.
00554                                         $wgOut->setRevisionId( $this->getRevIdFetched() );
00555                                         # Preload timestamp to avoid a DB hit
00556                                         $wgOut->setRevisionTimestamp( $this->getTimestamp() );
00557 
00558                                         # Pages containing custom CSS or JavaScript get special treatment
00559                                         if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
00560                                                 wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
00561                                                 $this->showCssOrJsPage();
00562                                                 $outputDone = true;
00563                                         } elseif( !wfRunHooks( 'ArticleViewCustom', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
00564                                                 # Allow extensions do their own custom view for certain pages
00565                                                 $outputDone = true;
00566                                         } else {
00567                                                 $text = $this->getContent();
00568                                                 $rt = Title::newFromRedirectArray( $text );
00569                                                 if ( $rt ) {
00570                                                         wfDebug( __METHOD__ . ": showing redirect=no page\n" );
00571                                                         # Viewing a redirect page (e.g. with parameter redirect=no)
00572                                                         $wgOut->addHTML( $this->viewRedirect( $rt ) );
00573                                                         # Parse just to get categories, displaytitle, etc.
00574                                                         $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
00575                                                         $wgOut->addParserOutputNoText( $this->mParserOutput );
00576                                                         $outputDone = true;
00577                                                 }
00578                                         }
00579                                         break;
00580                                 case 4:
00581                                         # Run the parse, protected by a pool counter
00582                                         wfDebug( __METHOD__ . ": doing uncached parse\n" );
00583 
00584                                         $poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
00585                                                 $this->getRevIdFetched(), $useParserCache, $this->getContent() );
00586 
00587                                         if ( !$poolArticleView->execute() ) {
00588                                                 $error = $poolArticleView->getError();
00589                                                 if ( $error ) {
00590                                                         $wgOut->clearHTML(); // for release() errors
00591                                                         $wgOut->enableClientCache( false );
00592                                                         $wgOut->setRobotPolicy( 'noindex,nofollow' );
00593 
00594                                                         $errortext = $error->getWikiText( false, 'view-pool-error' );
00595                                                         $wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
00596                                                 }
00597                                                 # Connection or timeout error
00598                                                 wfProfileOut( __METHOD__ );
00599                                                 return;
00600                                         }
00601 
00602                                         $this->mParserOutput = $poolArticleView->getParserOutput();
00603                                         $wgOut->addParserOutput( $this->mParserOutput );
00604 
00605                                         # Don't cache a dirty ParserOutput object
00606                                         if ( $poolArticleView->getIsDirty() ) {
00607                                                 $wgOut->setSquidMaxage( 0 );
00608                                                 $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
00609                                         }
00610 
00611                                         $outputDone = true;
00612                                         break;
00613                                 # Should be unreachable, but just in case...
00614                                 default:
00615                                         break 2;
00616                         }
00617                 }
00618 
00619                 # Get the ParserOutput actually *displayed* here.
00620                 # Note that $this->mParserOutput is the *current* version output.
00621                 $pOutput = ( $outputDone instanceof ParserOutput )
00622                         ? $outputDone // object fetched by hook
00623                         : $this->mParserOutput;
00624 
00625                 # Adjust title for main page & pages with displaytitle
00626                 if ( $pOutput ) {
00627                         $this->adjustDisplayTitle( $pOutput );
00628                 }
00629 
00630                 # For the main page, overwrite the <title> element with the con-
00631                 # tents of 'pagetitle-view-mainpage' instead of the default (if
00632                 # that's not empty).
00633                 # This message always exists because it is in the i18n files
00634                 if ( $this->getTitle()->isMainPage() ) {
00635                         $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
00636                         if ( !$msg->isDisabled() ) {
00637                                 $wgOut->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
00638                         }
00639                 }
00640 
00641                 # Check for any __NOINDEX__ tags on the page using $pOutput
00642                 $policy = $this->getRobotPolicy( 'view', $pOutput );
00643                 $wgOut->setIndexPolicy( $policy['index'] );
00644                 $wgOut->setFollowPolicy( $policy['follow'] );
00645 
00646                 $this->showViewFooter();
00647                 $this->mPage->doViewUpdates( $wgUser );
00648 
00649                 wfProfileOut( __METHOD__ );
00650         }
00651 
00656         public function adjustDisplayTitle( ParserOutput $pOutput ) {
00657                 global $wgOut;
00658                 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
00659                 $titleText = $pOutput->getTitleText();
00660                 if ( strval( $titleText ) !== '' ) {
00661                         $wgOut->setPageTitle( $titleText );
00662                 }
00663         }
00664 
00669         public function showDiffPage() {
00670                 global $wgRequest, $wgUser;
00671 
00672                 $diff = $wgRequest->getVal( 'diff' );
00673                 $rcid = $wgRequest->getVal( 'rcid' );
00674                 $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
00675                 $purge = $wgRequest->getVal( 'action' ) == 'purge';
00676                 $unhide = $wgRequest->getInt( 'unhide' ) == 1;
00677                 $oldid = $this->getOldID();
00678 
00679                 $de = new DifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
00680                 // DifferenceEngine directly fetched the revision:
00681                 $this->mRevIdFetched = $de->mNewid;
00682                 $de->showDiffPage( $diffOnly );
00683 
00684                 if ( $diff == 0 || $diff == $this->mPage->getLatest() ) {
00685                         # Run view updates for current revision only
00686                         $this->mPage->doViewUpdates( $wgUser );
00687                 }
00688         }
00689 
00697         protected function showCssOrJsPage() {
00698                 global $wgOut;
00699 
00700                 $dir = $this->getContext()->getLanguage()->getDir();
00701                 $lang = $this->getContext()->getLanguage()->getCode();
00702 
00703                 $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
00704                         'clearyourcache' );
00705 
00706                 // Give hooks a chance to customise the output
00707                 if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
00708                         // Wrap the whole lot in a <pre> and don't parse
00709                         $m = array();
00710                         preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
00711                         $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
00712                         $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
00713                         $wgOut->addHTML( "\n</pre>\n" );
00714                 }
00715         }
00716 
00724         public function getRobotPolicy( $action, $pOutput ) {
00725                 global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies;
00726                 global $wgDefaultRobotPolicy, $wgRequest;
00727 
00728                 $ns = $this->getTitle()->getNamespace();
00729 
00730                 if ( $ns == NS_USER || $ns == NS_USER_TALK ) {
00731                         # Don't index user and user talk pages for blocked users (bug 11443)
00732                         if ( !$this->getTitle()->isSubpage() ) {
00733                                 if ( Block::newFromTarget( null, $this->getTitle()->getText() ) instanceof Block ) {
00734                                         return array(
00735                                                 'index'  => 'noindex',
00736                                                 'follow' => 'nofollow'
00737                                         );
00738                                 }
00739                         }
00740                 }
00741 
00742                 if ( $this->mPage->getID() === 0 || $this->getOldID() ) {
00743                         # Non-articles (special pages etc), and old revisions
00744                         return array(
00745                                 'index'  => 'noindex',
00746                                 'follow' => 'nofollow'
00747                         );
00748                 } elseif ( $wgOut->isPrintable() ) {
00749                         # Discourage indexing of printable versions, but encourage following
00750                         return array(
00751                                 'index'  => 'noindex',
00752                                 'follow' => 'follow'
00753                         );
00754                 } elseif ( $wgRequest->getInt( 'curid' ) ) {
00755                         # For ?curid=x urls, disallow indexing
00756                         return array(
00757                                 'index'  => 'noindex',
00758                                 'follow' => 'follow'
00759                         );
00760                 }
00761 
00762                 # Otherwise, construct the policy based on the various config variables.
00763                 $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
00764 
00765                 if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
00766                         # Honour customised robot policies for this namespace
00767                         $policy = array_merge(
00768                                 $policy,
00769                                 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
00770                         );
00771                 }
00772                 if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
00773                         # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
00774                         # a final sanity check that we have really got the parser output.
00775                         $policy = array_merge(
00776                                 $policy,
00777                                 array( 'index' => $pOutput->getIndexPolicy() )
00778                         );
00779                 }
00780 
00781                 if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
00782                         # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
00783                         $policy = array_merge(
00784                                 $policy,
00785                                 self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
00786                         );
00787                 }
00788 
00789                 return $policy;
00790         }
00791 
00799         public static function formatRobotPolicy( $policy ) {
00800                 if ( is_array( $policy ) ) {
00801                         return $policy;
00802                 } elseif ( !$policy ) {
00803                         return array();
00804                 }
00805 
00806                 $policy = explode( ',', $policy );
00807                 $policy = array_map( 'trim', $policy );
00808 
00809                 $arr = array();
00810                 foreach ( $policy as $var ) {
00811                         if ( in_array( $var, array( 'index', 'noindex' ) ) ) {
00812                                 $arr['index'] = $var;
00813                         } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) {
00814                                 $arr['follow'] = $var;
00815                         }
00816                 }
00817 
00818                 return $arr;
00819         }
00820 
00828         public function showRedirectedFromHeader() {
00829                 global $wgOut, $wgRequest, $wgRedirectSources;
00830 
00831                 $rdfrom = $wgRequest->getVal( 'rdfrom' );
00832 
00833                 if ( isset( $this->mRedirectedFrom ) ) {
00834                         // This is an internally redirected page view.
00835                         // We'll need a backlink to the source page for navigation.
00836                         if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
00837                                 $redir = Linker::linkKnown(
00838                                         $this->mRedirectedFrom,
00839                                         null,
00840                                         array(),
00841                                         array( 'redirect' => 'no' )
00842                                 );
00843 
00844                                 $wgOut->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
00845 
00846                                 // Set the fragment if one was specified in the redirect
00847                                 if ( strval( $this->getTitle()->getFragment() ) != '' ) {
00848                                         $fragment = Xml::escapeJsString( $this->getTitle()->getFragmentForURL() );
00849                                         $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" );
00850                                 }
00851 
00852                                 // Add a <link rel="canonical"> tag
00853                                 $wgOut->addLink( array( 'rel' => 'canonical',
00854                                         'href' => $this->getTitle()->getLocalURL() )
00855                                 );
00856 
00857                                 // Tell $wgOut the user arrived at this article through a redirect
00858                                 $wgOut->setRedirectedFrom( $this->mRedirectedFrom );
00859 
00860                                 return true;
00861                         }
00862                 } elseif ( $rdfrom ) {
00863                         // This is an externally redirected view, from some other wiki.
00864                         // If it was reported from a trusted site, supply a backlink.
00865                         if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
00866                                 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
00867                                 $wgOut->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
00868 
00869                                 return true;
00870                         }
00871                 }
00872 
00873                 return false;
00874         }
00875 
00880         public function showNamespaceHeader() {
00881                 global $wgOut;
00882 
00883                 if ( $this->getTitle()->isTalkPage() ) {
00884                         if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
00885                                 $wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1\n</div>", array( 'talkpageheader' ) );
00886                         }
00887                 }
00888         }
00889 
00893         public function showViewFooter() {
00894                 global $wgOut;
00895 
00896                 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
00897                 if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) {
00898                         $wgOut->addWikiMsg( 'anontalkpagetext' );
00899                 }
00900 
00901                 # If we have been passed an &rcid= parameter, we want to give the user a
00902                 # chance to mark this new article as patrolled.
00903                 $this->showPatrolFooter();
00904 
00905                 wfRunHooks( 'ArticleViewFooter', array( $this ) );
00906 
00907         }
00908 
00914         public function showPatrolFooter() {
00915                 global $wgOut, $wgRequest, $wgUser;
00916 
00917                 $rcid = $wgRequest->getVal( 'rcid' );
00918 
00919                 if ( !$rcid || !$this->getTitle()->quickUserCan( 'patrol' ) ) {
00920                         return;
00921                 }
00922 
00923                 $token = $wgUser->getEditToken( $rcid );
00924                 $wgOut->preventClickjacking();
00925 
00926                 $wgOut->addHTML(
00927                         "<div class='patrollink'>" .
00928                                 wfMsgHtml(
00929                                         'markaspatrolledlink',
00930                                         Linker::link(
00931                                                 $this->getTitle(),
00932                                                 wfMsgHtml( 'markaspatrolledtext' ),
00933                                                 array(),
00934                                                 array(
00935                                                         'action' => 'markpatrolled',
00936                                                         'rcid' => $rcid,
00937                                                         'token' => $token,
00938                                                 ),
00939                                                 array( 'known', 'noclasses' )
00940                                         )
00941                                 ) .
00942                         '</div>'
00943                 );
00944         }
00945 
00950         public function showMissingArticle() {
00951                 global $wgOut, $wgRequest, $wgUser, $wgSend404Code;
00952 
00953                 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
00954                 if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) {
00955                         $parts = explode( '/', $this->getTitle()->getText() );
00956                         $rootPart = $parts[0];
00957                         $user = User::newFromName( $rootPart, false /* allow IP users*/ );
00958                         $ip = User::isIP( $rootPart );
00959 
00960                         if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
00961                                 $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
00962                                         array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) );
00963                         } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
00964                                 LogEventsList::showLogExtract(
00965                                         $wgOut,
00966                                         'block',
00967                                         $user->getUserPage()->getPrefixedText(),
00968                                         '',
00969                                         array(
00970                                                 'lim' => 1,
00971                                                 'showIfEmpty' => false,
00972                                                 'msgKey' => array(
00973                                                         'blocked-notice-logextract',
00974                                                         $user->getName() # Support GENDER in notice
00975                                                 )
00976                                         )
00977                                 );
00978                         }
00979                 }
00980 
00981                 wfRunHooks( 'ShowMissingArticle', array( $this ) );
00982 
00983                 # Show delete and move logs
00984                 LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->getTitle()->getPrefixedText(), '',
00985                         array(  'lim' => 10,
00986                                 'conds' => array( "log_action != 'revision'" ),
00987                                 'showIfEmpty' => false,
00988                                 'msgKey' => array( 'moveddeleted-notice' ) )
00989                 );
00990 
00991                 if ( !$this->mPage->hasViewableContent() && $wgSend404Code ) {
00992                         // If there's no backing content, send a 404 Not Found
00993                         // for better machine handling of broken links.
00994                         $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
00995                 }
00996 
00997                 $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
00998 
00999                 if ( ! $hookResult ) {
01000                         return;
01001                 }
01002 
01003                 # Show error message
01004                 $oldid = $this->getOldID();
01005                 if ( $oldid ) {
01006                         $text = wfMsgNoTrans( 'missing-article',
01007                                 $this->getTitle()->getPrefixedText(),
01008                                 wfMsgNoTrans( 'missingarticle-rev', $oldid ) );
01009                 } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) {
01010                         // Use the default message text
01011                         $text = $this->getTitle()->getDefaultMessageText();
01012                 } else {
01013                         $createErrors = $this->getTitle()->getUserPermissionsErrors( 'create', $wgUser );
01014                         $editErrors = $this->getTitle()->getUserPermissionsErrors( 'edit', $wgUser );
01015                         $errors = array_merge( $createErrors, $editErrors );
01016 
01017                         if ( !count( $errors ) ) {
01018                                 $text = wfMsgNoTrans( 'noarticletext' );
01019                         } else {
01020                                 $text = wfMsgNoTrans( 'noarticletext-nopermission' );
01021                         }
01022                 }
01023                 $text = "<div class='noarticletext'>\n$text\n</div>";
01024 
01025                 $wgOut->addWikiText( $text );
01026         }
01027 
01034         public function showDeletedRevisionHeader() {
01035                 global $wgOut, $wgRequest;
01036 
01037                 if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
01038                         // Not deleted
01039                         return true;
01040                 }
01041 
01042                 // If the user is not allowed to see it...
01043                 if ( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) {
01044                         $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
01045                                 'rev-deleted-text-permission' );
01046 
01047                         return false;
01048                 // If the user needs to confirm that they want to see it...
01049                 } elseif ( $wgRequest->getInt( 'unhide' ) != 1 ) {
01050                         # Give explanation and add a link to view the revision...
01051                         $oldid = intval( $this->getOldID() );
01052                         $link = $this->getTitle()->getFullUrl( "oldid={$oldid}&unhide=1" );
01053                         $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
01054                                 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
01055                         $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
01056                                 array( $msg, $link ) );
01057 
01058                         return false;
01059                 // We are allowed to see...
01060                 } else {
01061                         $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
01062                                 'rev-suppressed-text-view' : 'rev-deleted-text-view';
01063                         $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
01064 
01065                         return true;
01066                 }
01067         }
01068 
01077         public function setOldSubtitle( $oldid = 0 ) {
01078                 global $wgLang, $wgOut, $wgUser, $wgRequest;
01079 
01080                 if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
01081                         return;
01082                 }
01083 
01084                 $unhide = $wgRequest->getInt( 'unhide' ) == 1;
01085 
01086                 # Cascade unhide param in links for easy deletion browsing
01087                 $extraParams = array();
01088                 if ( $wgRequest->getVal( 'unhide' ) ) {
01089                         $extraParams['unhide'] = 1;
01090                 }
01091 
01092                 $revision = Revision::newFromId( $oldid );
01093                 $timestamp = $revision->getTimestamp();
01094 
01095                 $current = ( $oldid == $this->mPage->getLatest() );
01096                 $td = $wgLang->timeanddate( $timestamp, true );
01097                 $tddate = $wgLang->date( $timestamp, true );
01098                 $tdtime = $wgLang->time( $timestamp, true );
01099 
01100                 # Show user links if allowed to see them. If hidden, then show them only if requested...
01101                 $userlinks = Linker::revUserTools( $revision, !$unhide );
01102 
01103                 $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
01104                         ? 'revision-info-current'
01105                         : 'revision-info';
01106 
01107                 $wgOut->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
01108                         $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
01109                         $tdtime, $revision->getUser() )->parse() . "</div>" );
01110 
01111                 $lnk = $current
01112                         ? wfMsgHtml( 'currentrevisionlink' )
01113                         : Linker::link(
01114                                 $this->getTitle(),
01115                                 wfMsgHtml( 'currentrevisionlink' ),
01116                                 array(),
01117                                 $extraParams,
01118                                 array( 'known', 'noclasses' )
01119                         );
01120                 $curdiff = $current
01121                         ? wfMsgHtml( 'diff' )
01122                         : Linker::link(
01123                                 $this->getTitle(),
01124                                 wfMsgHtml( 'diff' ),
01125                                 array(),
01126                                 array(
01127                                         'diff' => 'cur',
01128                                         'oldid' => $oldid
01129                                 ) + $extraParams,
01130                                 array( 'known', 'noclasses' )
01131                         );
01132                 $prev = $this->getTitle()->getPreviousRevisionID( $oldid ) ;
01133                 $prevlink = $prev
01134                         ? Linker::link(
01135                                 $this->getTitle(),
01136                                 wfMsgHtml( 'previousrevision' ),
01137                                 array(),
01138                                 array(
01139                                         'direction' => 'prev',
01140                                         'oldid' => $oldid
01141                                 ) + $extraParams,
01142                                 array( 'known', 'noclasses' )
01143                         )
01144                         : wfMsgHtml( 'previousrevision' );
01145                 $prevdiff = $prev
01146                         ? Linker::link(
01147                                 $this->getTitle(),
01148                                 wfMsgHtml( 'diff' ),
01149                                 array(),
01150                                 array(
01151                                         'diff' => 'prev',
01152                                         'oldid' => $oldid
01153                                 ) + $extraParams,
01154                                 array( 'known', 'noclasses' )
01155                         )
01156                         : wfMsgHtml( 'diff' );
01157                 $nextlink = $current
01158                         ? wfMsgHtml( 'nextrevision' )
01159                         : Linker::link(
01160                                 $this->getTitle(),
01161                                 wfMsgHtml( 'nextrevision' ),
01162                                 array(),
01163                                 array(
01164                                         'direction' => 'next',
01165                                         'oldid' => $oldid
01166                                 ) + $extraParams,
01167                                 array( 'known', 'noclasses' )
01168                         );
01169                 $nextdiff = $current
01170                         ? wfMsgHtml( 'diff' )
01171                         : Linker::link(
01172                                 $this->getTitle(),
01173                                 wfMsgHtml( 'diff' ),
01174                                 array(),
01175                                 array(
01176                                         'diff' => 'next',
01177                                         'oldid' => $oldid
01178                                 ) + $extraParams,
01179                                 array( 'known', 'noclasses' )
01180                         );
01181 
01182                 $cdel = Linker::getRevDeleteLink( $wgUser, $revision, $this->getTitle() );
01183                 if ( $cdel !== '' ) {
01184                         $cdel .= ' ';
01185                 }
01186 
01187                 $wgOut->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
01188                         wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ),
01189                         $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>" );
01190         }
01191 
01200         public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
01201                 global $wgOut, $wgStylePath;
01202 
01203                 if ( !is_array( $target ) ) {
01204                         $target = array( $target );
01205                 }
01206 
01207                 $lang = $this->getTitle()->getPageLanguage();
01208                 $imageDir = $lang->getDir();
01209 
01210                 if ( $appendSubtitle ) {
01211                         $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
01212                 }
01213 
01214                 // the loop prepends the arrow image before the link, so the first case needs to be outside
01215 
01219                 $title = array_shift( $target );
01220 
01221                 if ( $forceKnown ) {
01222                         $link = Linker::linkKnown( $title, htmlspecialchars( $title->getFullText() ) );
01223                 } else {
01224                         $link = Linker::link( $title, htmlspecialchars( $title->getFullText() ) );
01225                 }
01226 
01227                 $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
01228                 $alt = $lang->isRTL() ? '←' : '→';
01229                 // Automatically append redirect=no to each link, since most of them are redirect pages themselves.
01230                 foreach ( $target as $rt ) {
01231                         $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) );
01232                         if ( $forceKnown ) {
01233                                 $link .= Linker::linkKnown( $rt, htmlspecialchars( $rt->getFullText(), array(), array( 'redirect' => 'no' ) ) );
01234                         } else {
01235                                 $link .= Linker::link( $rt, htmlspecialchars( $rt->getFullText() ), array(), array( 'redirect' => 'no' ) );
01236                         }
01237                 }
01238 
01239                 $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
01240                 return '<div class="redirectMsg">' .
01241                         Html::element( 'img', array( 'src' => $imageUrl, 'alt' => '#REDIRECT' ) ) .
01242                         '<span class="redirectText">' . $link . '</span></div>';
01243         }
01244 
01248         public function render() {
01249                 global $wgOut;
01250 
01251                 $wgOut->setArticleBodyOnly( true );
01252                 $this->view();
01253         }
01254 
01258         public function protect() {
01259                 $form = new ProtectionForm( $this );
01260                 $form->execute();
01261         }
01262 
01266         public function unprotect() {
01267                 $this->protect();
01268         }
01269 
01273         public function delete() {
01274                 global $wgOut, $wgRequest, $wgLang;
01275 
01276                 # This code desperately needs to be totally rewritten
01277 
01278                 $title = $this->getTitle();
01279                 $user = $this->getContext()->getUser();
01280 
01281                 # Check permissions
01282                 $permission_errors = $title->getUserPermissionsErrors( 'delete', $user );
01283                 if ( count( $permission_errors ) ) {
01284                         throw new PermissionsError( 'delete', $permission_errors );
01285                 }
01286 
01287                 # Read-only check...
01288                 if ( wfReadOnly() ) {
01289                         throw new ReadOnlyError;
01290                 }
01291 
01292                 # Better double-check that it hasn't been deleted yet!
01293                 $dbw = wfGetDB( DB_MASTER );
01294                 $conds = $title->pageCond();
01295                 $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
01296                 if ( $latest === false ) {
01297                         $wgOut->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) );
01298                         $wgOut->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
01299                                         array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) )
01300                                 );
01301                         $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
01302                         LogEventsList::showLogExtract(
01303                                 $wgOut,
01304                                 'delete',
01305                                 $title->getPrefixedText()
01306                         );
01307 
01308                         return;
01309                 }
01310 
01311                 $deleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
01312                 $deleteReason = $wgRequest->getText( 'wpReason' );
01313 
01314                 if ( $deleteReasonList == 'other' ) {
01315                         $reason = $deleteReason;
01316                 } elseif ( $deleteReason != '' ) {
01317                         // Entry from drop down menu + additional comment
01318                         $reason = $deleteReasonList . wfMsgForContent( 'colon-separator' ) . $deleteReason;
01319                 } else {
01320                         $reason = $deleteReasonList;
01321                 }
01322 
01323                 if ( $wgRequest->wasPosted() && $user->matchEditToken( $wgRequest->getVal( 'wpEditToken' ),
01324                         array( 'delete', $this->getTitle()->getPrefixedText() ) ) )
01325                 {
01326                         # Flag to hide all contents of the archived revisions
01327                         $suppress = $wgRequest->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
01328 
01329                         $this->doDelete( $reason, $suppress );
01330 
01331                         if ( $wgRequest->getCheck( 'wpWatch' ) && $user->isLoggedIn() ) {
01332                                 $this->doWatch();
01333                         } elseif ( $title->userIsWatching() ) {
01334                                 $this->doUnwatch();
01335                         }
01336 
01337                         return;
01338                 }
01339 
01340                 // Generate deletion reason
01341                 $hasHistory = false;
01342                 if ( !$reason ) {
01343                         $reason = $this->generateReason( $hasHistory );
01344                 }
01345 
01346                 // If the page has a history, insert a warning
01347                 if ( $hasHistory ) {
01348                         $revisions = $this->mTitle->estimateRevisionCount();
01349                         // @todo FIXME: i18n issue/patchwork message
01350                         $wgOut->addHTML( '<strong class="mw-delete-warning-revisions">' .
01351                                 wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) .
01352                                 wfMsgHtml( 'word-separator' ) . Linker::link( $title,
01353                                         wfMsgHtml( 'history' ),
01354                                         array( 'rel' => 'archives' ),
01355                                         array( 'action' => 'history' ) ) .
01356                                 '</strong>'
01357                         );
01358 
01359                         if ( $this->mTitle->isBigDeletion() ) {
01360                                 global $wgDeleteRevisionsLimit;
01361                                 $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
01362                                         array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
01363                         }
01364                 }
01365 
01366                 return $this->confirmDelete( $reason );
01367         }
01368 
01374         public function confirmDelete( $reason ) {
01375                 global $wgOut;
01376 
01377                 wfDebug( "Article::confirmDelete\n" );
01378 
01379                 $wgOut->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
01380                 $wgOut->addBacklinkSubtitle( $this->getTitle() );
01381                 $wgOut->setRobotPolicy( 'noindex,nofollow' );
01382                 $wgOut->addWikiMsg( 'confirmdeletetext' );
01383 
01384                 wfRunHooks( 'ArticleConfirmDelete', array( $this, $wgOut, &$reason ) );
01385 
01386                 $user = $this->getContext()->getUser();
01387 
01388                 if ( $user->isAllowed( 'suppressrevision' ) ) {
01389                         $suppress = "<tr id=\"wpDeleteSuppressRow\">
01390                                         <td></td>
01391                                         <td class='mw-input'><strong>" .
01392                                                 Xml::checkLabel( wfMsg( 'revdelete-suppress' ),
01393                                                         'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
01394                                         "</strong></td>
01395                                 </tr>";
01396                 } else {
01397                         $suppress = '';
01398                 }
01399                 $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching();
01400 
01401                 $form = Xml::openElement( 'form', array( 'method' => 'post',
01402                         'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
01403                         Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
01404                         Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) .
01405                         Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
01406                         "<tr id=\"wpDeleteReasonListRow\">
01407                                 <td class='mw-label'>" .
01408                                         Xml::label( wfMsg( 'deletecomment' ), 'wpDeleteReasonList' ) .
01409                                 "</td>
01410                                 <td class='mw-input'>" .
01411                                         Xml::listDropDown( 'wpDeleteReasonList',
01412                                                 wfMsgForContent( 'deletereason-dropdown' ),
01413                                                 wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) .
01414                                 "</td>
01415                         </tr>
01416                         <tr id=\"wpDeleteReasonRow\">
01417                                 <td class='mw-label'>" .
01418                                         Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) .
01419                                 "</td>
01420                                 <td class='mw-input'>" .
01421                                 Html::input( 'wpReason', $reason, 'text', array(
01422                                         'size' => '60',
01423                                         'maxlength' => '255',
01424                                         'tabindex' => '2',
01425                                         'id' => 'wpReason',
01426                                         'autofocus'
01427                                 ) ) .
01428                                 "</td>
01429                         </tr>";
01430 
01431                 # Disallow watching if user is not logged in
01432                 if ( $user->isLoggedIn() ) {
01433                         $form .= "
01434                         <tr>
01435                                 <td></td>
01436                                 <td class='mw-input'>" .
01437                                         Xml::checkLabel( wfMsg( 'watchthis' ),
01438                                                 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
01439                                 "</td>
01440                         </tr>";
01441                 }
01442 
01443                 $form .= "
01444                         $suppress
01445                         <tr>
01446                                 <td></td>
01447                                 <td class='mw-submit'>" .
01448                                         Xml::submitButton( wfMsg( 'deletepage' ),
01449                                                 array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
01450                                 "</td>
01451                         </tr>" .
01452                         Xml::closeElement( 'table' ) .
01453                         Xml::closeElement( 'fieldset' ) .
01454                         Html::hidden( 'wpEditToken', $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) ) ) .
01455                         Xml::closeElement( 'form' );
01456 
01457                         if ( $user->isAllowed( 'editinterface' ) ) {
01458                                 $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
01459                                 $link = Linker::link(
01460                                         $title,
01461                                         wfMsgHtml( 'delete-edit-reasonlist' ),
01462                                         array(),
01463                                         array( 'action' => 'edit' )
01464                                 );
01465                                 $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
01466                         }
01467 
01468                 $wgOut->addHTML( $form );
01469                 $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
01470                 LogEventsList::showLogExtract( $wgOut, 'delete',
01471                         $this->getTitle()->getPrefixedText()
01472                 );
01473         }
01474 
01480         public function doDelete( $reason, $suppress = false ) {
01481                 global $wgOut;
01482 
01483                 $error = '';
01484                 if ( $this->mPage->doDeleteArticle( $reason, $suppress, 0, true, $error ) ) {
01485                         $deleted = $this->getTitle()->getPrefixedText();
01486 
01487                         $wgOut->setPageTitle( wfMessage( 'actioncomplete' ) );
01488                         $wgOut->setRobotPolicy( 'noindex,nofollow' );
01489 
01490                         $loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
01491 
01492                         $wgOut->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
01493                         $wgOut->returnToMain( false );
01494                 } else {
01495                         $wgOut->setPageTitle( wfMessage( 'cannotdelete-title', $this->getTitle()->getPrefixedText() ) );
01496                         if ( $error == '' ) {
01497                                 $wgOut->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
01498                                         array( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
01499                                 );
01500                                 $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
01501 
01502                                 LogEventsList::showLogExtract(
01503                                         $wgOut,
01504                                         'delete',
01505                                         $this->getTitle()->getPrefixedText()
01506                                 );
01507                         } else {
01508                                 $wgOut->addHTML( $error );
01509                         }
01510                 }
01511         }
01512 
01513         /* Caching functions */
01514 
01522         protected function tryFileCache() {
01523                 static $called = false;
01524 
01525                 if ( $called ) {
01526                         wfDebug( "Article::tryFileCache(): called twice!?\n" );
01527                         return false;
01528                 }
01529 
01530                 $called = true;
01531                 if ( $this->isFileCacheable() ) {
01532                         $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'view' );
01533                         if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
01534                                 wfDebug( "Article::tryFileCache(): about to load file\n" );
01535                                 $cache->loadFromFileCache( $this->getContext() );
01536                                 return true;
01537                         } else {
01538                                 wfDebug( "Article::tryFileCache(): starting buffer\n" );
01539                                 ob_start( array( &$cache, 'saveToFileCache' ) );
01540                         }
01541                 } else {
01542                         wfDebug( "Article::tryFileCache(): not cacheable\n" );
01543                 }
01544 
01545                 return false;
01546         }
01547 
01552         public function isFileCacheable() {
01553                 $cacheable = false;
01554 
01555                 if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
01556                         $cacheable = $this->mPage->getID()
01557                                 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
01558                         // Extension may have reason to disable file caching on some pages.
01559                         if ( $cacheable ) {
01560                                 $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
01561                         }
01562                 }
01563 
01564                 return $cacheable;
01565         }
01566 
01580         public function getParserOutput( $oldid = null, User $user = null ) {
01581                 global $wgUser;
01582 
01583                 $user = is_null( $user ) ? $wgUser : $user;
01584                 $parserOptions = $this->mPage->makeParserOptions( $user );
01585 
01586                 return $this->mPage->getParserOutput( $parserOptions, $oldid );
01587         }
01588 
01593         public function getParserOptions() {
01594                 global $wgUser;
01595                 if ( !$this->mParserOptions ) {
01596                         $this->mParserOptions = $this->mPage->makeParserOptions( $wgUser );
01597                 }
01598                 // Clone to allow modifications of the return value without affecting cache
01599                 return clone $this->mParserOptions;
01600         }
01601 
01608         public function setContext( $context ) {
01609                 $this->mContext = $context;
01610         }
01611 
01618         public function getContext() {
01619                 if ( $this->mContext instanceof IContextSource ) {
01620                         return $this->mContext;
01621                 } else {
01622                         wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
01623                         return RequestContext::getMain();
01624                 }
01625         }
01626 
01631         public function info() {
01632                 wfDeprecated( __METHOD__, '1.19' );
01633                 Action::factory( 'info', $this )->show();
01634         }
01635 
01640         public function markpatrolled() {
01641                 wfDeprecated( __METHOD__, '1.18' );
01642                 Action::factory( 'markpatrolled', $this )->show();
01643         }
01644 
01649         public function purge() {
01650                 return Action::factory( 'purge', $this )->show();
01651         }
01652 
01657         public function revert() {
01658                 wfDeprecated( __METHOD__, '1.19' );
01659                 Action::factory( 'revert', $this )->show();
01660         }
01661 
01666         public function rollback() {
01667                 wfDeprecated( __METHOD__, '1.19' );
01668                 Action::factory( 'rollback', $this )->show();
01669         }
01670 
01676         public function watch() {
01677                 wfDeprecated( __METHOD__, '1.18' );
01678                 Action::factory( 'watch', $this )->show();
01679         }
01680 
01689         public function doWatch() {
01690                 global $wgUser;
01691                 wfDeprecated( __METHOD__, '1.18' );
01692                 return WatchAction::doWatch( $this->getTitle(), $wgUser );
01693         }
01694 
01700         public function unwatch() {
01701                 wfDeprecated( __METHOD__, '1.18' );
01702                 Action::factory( 'unwatch', $this )->show();
01703         }
01704 
01710         public function doUnwatch() {
01711                 global $wgUser;
01712                 wfDeprecated( __METHOD__, '1.18' );
01713                 return WatchAction::doUnwatch( $this->getTitle(), $wgUser );
01714         }
01715 
01725         public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
01726                 wfDeprecated( __METHOD__, '1.18' );
01727                 global $wgOut;
01728 
01729                 if ( $noRedir ) {
01730                         $query = 'redirect=no';
01731                         if ( $extraQuery )
01732                                 $query .= "&$extraQuery";
01733                 } else {
01734                         $query = $extraQuery;
01735                 }
01736 
01737                 $wgOut->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor );
01738         }
01739 
01746         public function __get( $fname ) {
01747                 if ( property_exists( $this->mPage, $fname ) ) {
01748                         #wfWarn( "Access to raw $fname field " . __CLASS__ );
01749                         return $this->mPage->$fname;
01750                 }
01751                 trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
01752         }
01753 
01761         public function __set( $fname, $fvalue ) {
01762                 if ( property_exists( $this->mPage, $fname ) ) {
01763                         #wfWarn( "Access to raw $fname field of " . __CLASS__ );
01764                         $this->mPage->$fname = $fvalue;
01765                 // Note: extensions may want to toss on new fields
01766                 } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
01767                         $this->mPage->$fname = $fvalue;
01768                 } else {
01769                         trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
01770                 }
01771         }
01772 
01780         public function __call( $fname, $args ) {
01781                 if ( is_callable( array( $this->mPage, $fname ) ) ) {
01782                         #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" );
01783                         return call_user_func_array( array( $this->mPage, $fname ), $args );
01784                 }
01785                 trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
01786         }
01787 
01788         // ****** B/C functions to work-around PHP silliness with __call and references ****** //
01789 
01798         public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
01799                 return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
01800         }
01801 
01809         public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
01810                 return $this->mPage->updateRestrictions( $limit, $reason, $cascade, $expiry );
01811         }
01812 
01821         public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
01822                 return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
01823         }
01824 
01834         public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
01835                 global $wgUser;
01836                 $user = is_null( $user ) ? $wgUser : $user;
01837                 return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
01838         }
01839 
01848         public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
01849                 global $wgUser;
01850                 $guser = is_null( $guser ) ? $wgUser : $guser;
01851                 return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
01852         }
01853 
01858         public function generateReason( &$hasHistory ) {
01859                 return $this->mPage->getAutoDeleteReason( $hasHistory );
01860         }
01861 
01862         // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
01863 
01867         public static function selectFields() {
01868                 return WikiPage::selectFields();
01869         }
01870 
01874         public static function onArticleCreate( $title ) {
01875                 WikiPage::onArticleCreate( $title );
01876         }
01877 
01881         public static function onArticleDelete( $title ) {
01882                 WikiPage::onArticleDelete( $title );
01883         }
01884 
01888         public static function onArticleEdit( $title ) {
01889                 WikiPage::onArticleEdit( $title );
01890         }
01891 
01898         public static function getAutosummary( $oldtext, $newtext, $flags ) {
01899                 return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
01900         }
01901         // ******
01902 }