MediaWiki
REL1_22
|
00001 <?php 00036 class Article implements Page { 00045 protected $mContext; 00046 00051 protected $mPage; 00052 00057 public $mParserOptions; 00058 00063 var $mContent; // !< #BC cruft 00064 00070 var $mContentObject; // !< 00071 00076 var $mContentLoaded = false; // !< 00077 00083 var $mOldId; // !< 00084 00089 var $mRedirectedFrom = null; 00090 00095 var $mRedirectUrl = false; // !< 00096 00101 var $mRevIdFetched = 0; // !< 00102 00107 var $mRevision = null; 00108 00113 var $mParserOutput; 00114 00122 public function __construct( Title $title, $oldId = null ) { 00123 $this->mOldId = $oldId; 00124 $this->mPage = $this->newPage( $title ); 00125 } 00126 00131 protected function newPage( Title $title ) { 00132 return new WikiPage( $title ); 00133 } 00134 00140 public static function newFromID( $id ) { 00141 $t = Title::newFromID( $id ); 00142 # @todo FIXME: Doesn't inherit right 00143 return $t == null ? null : new self( $t ); 00144 # return $t == null ? null : new static( $t ); // PHP 5.3 00145 } 00146 00154 public static function newFromTitle( $title, IContextSource $context ) { 00155 if ( NS_MEDIA == $title->getNamespace() ) { 00156 // FIXME: where should this go? 00157 $title = Title::makeTitle( NS_FILE, $title->getDBkey() ); 00158 } 00159 00160 $page = null; 00161 wfRunHooks( 'ArticleFromTitle', array( &$title, &$page ) ); 00162 if ( !$page ) { 00163 switch ( $title->getNamespace() ) { 00164 case NS_FILE: 00165 $page = new ImagePage( $title ); 00166 break; 00167 case NS_CATEGORY: 00168 $page = new CategoryPage( $title ); 00169 break; 00170 default: 00171 $page = new Article( $title ); 00172 } 00173 } 00174 $page->setContext( $context ); 00175 00176 return $page; 00177 } 00178 00186 public static function newFromWikiPage( WikiPage $page, IContextSource $context ) { 00187 $article = self::newFromTitle( $page->getTitle(), $context ); 00188 $article->mPage = $page; // override to keep process cached vars 00189 return $article; 00190 } 00191 00197 public function setRedirectedFrom( Title $from ) { 00198 $this->mRedirectedFrom = $from; 00199 } 00200 00206 public function getTitle() { 00207 return $this->mPage->getTitle(); 00208 } 00209 00216 public function getPage() { 00217 return $this->mPage; 00218 } 00219 00223 public function clear() { 00224 $this->mContentLoaded = false; 00225 00226 $this->mRedirectedFrom = null; # Title object if set 00227 $this->mRevIdFetched = 0; 00228 $this->mRedirectUrl = false; 00229 00230 $this->mPage->clear(); 00231 } 00232 00245 public function getContent() { 00246 ContentHandler::deprecated( __METHOD__, '1.21' ); 00247 $content = $this->getContentObject(); 00248 return ContentHandler::getContentText( $content ); 00249 } 00250 00266 protected function getContentObject() { 00267 wfProfileIn( __METHOD__ ); 00268 00269 if ( $this->mPage->getID() === 0 ) { 00270 # If this is a MediaWiki:x message, then load the messages 00271 # and return the message value for x. 00272 if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) { 00273 $text = $this->getTitle()->getDefaultMessageText(); 00274 if ( $text === false ) { 00275 $text = ''; 00276 } 00277 00278 $content = ContentHandler::makeContent( $text, $this->getTitle() ); 00279 } else { 00280 $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon'; 00281 $content = new MessageContent( $message, null, 'parsemag' ); 00282 } 00283 } else { 00284 $this->fetchContentObject(); 00285 $content = $this->mContentObject; 00286 } 00287 00288 wfProfileOut( __METHOD__ ); 00289 return $content; 00290 } 00291 00296 public function getOldID() { 00297 if ( is_null( $this->mOldId ) ) { 00298 $this->mOldId = $this->getOldIDFromRequest(); 00299 } 00300 00301 return $this->mOldId; 00302 } 00303 00309 public function getOldIDFromRequest() { 00310 $this->mRedirectUrl = false; 00311 00312 $request = $this->getContext()->getRequest(); 00313 $oldid = $request->getIntOrNull( 'oldid' ); 00314 00315 if ( $oldid === null ) { 00316 return 0; 00317 } 00318 00319 if ( $oldid !== 0 ) { 00320 # Load the given revision and check whether the page is another one. 00321 # In that case, update this instance to reflect the change. 00322 if ( $oldid === $this->mPage->getLatest() ) { 00323 $this->mRevision = $this->mPage->getRevision(); 00324 } else { 00325 $this->mRevision = Revision::newFromId( $oldid ); 00326 if ( $this->mRevision !== null ) { 00327 // Revision title doesn't match the page title given? 00328 if ( $this->mPage->getID() != $this->mRevision->getPage() ) { 00329 $function = array( get_class( $this->mPage ), 'newFromID' ); 00330 $this->mPage = call_user_func( $function, $this->mRevision->getPage() ); 00331 } 00332 } 00333 } 00334 } 00335 00336 if ( $request->getVal( 'direction' ) == 'next' ) { 00337 $nextid = $this->getTitle()->getNextRevisionID( $oldid ); 00338 if ( $nextid ) { 00339 $oldid = $nextid; 00340 $this->mRevision = null; 00341 } else { 00342 $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' ); 00343 } 00344 } elseif ( $request->getVal( 'direction' ) == 'prev' ) { 00345 $previd = $this->getTitle()->getPreviousRevisionID( $oldid ); 00346 if ( $previd ) { 00347 $oldid = $previd; 00348 $this->mRevision = null; 00349 } 00350 } 00351 00352 return $oldid; 00353 } 00354 00360 function loadContent() { 00361 wfDeprecated( __METHOD__, '1.19' ); 00362 $this->fetchContent(); 00363 } 00364 00377 function fetchContent() { #BC cruft! 00378 ContentHandler::deprecated( __METHOD__, '1.21' ); 00379 00380 if ( $this->mContentLoaded && $this->mContent ) { 00381 return $this->mContent; 00382 } 00383 00384 wfProfileIn( __METHOD__ ); 00385 00386 $content = $this->fetchContentObject(); 00387 00388 // @todo Get rid of mContent everywhere! 00389 $this->mContent = ContentHandler::getContentText( $content ); 00390 ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); 00391 00392 wfProfileOut( __METHOD__ ); 00393 00394 return $this->mContent; 00395 } 00396 00408 protected function fetchContentObject() { 00409 if ( $this->mContentLoaded ) { 00410 return $this->mContentObject; 00411 } 00412 00413 wfProfileIn( __METHOD__ ); 00414 00415 $this->mContentLoaded = true; 00416 $this->mContent = null; 00417 00418 $oldid = $this->getOldID(); 00419 00420 # Pre-fill content with error message so that if something 00421 # fails we'll have something telling us what we intended. 00422 //XXX: this isn't page content but a UI message. horrible. 00423 $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() ); 00424 00425 if ( $oldid ) { 00426 # $this->mRevision might already be fetched by getOldIDFromRequest() 00427 if ( !$this->mRevision ) { 00428 $this->mRevision = Revision::newFromId( $oldid ); 00429 if ( !$this->mRevision ) { 00430 wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" ); 00431 wfProfileOut( __METHOD__ ); 00432 return false; 00433 } 00434 } 00435 } else { 00436 if ( !$this->mPage->getLatest() ) { 00437 wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" ); 00438 wfProfileOut( __METHOD__ ); 00439 return false; 00440 } 00441 00442 $this->mRevision = $this->mPage->getRevision(); 00443 00444 if ( !$this->mRevision ) { 00445 wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" ); 00446 wfProfileOut( __METHOD__ ); 00447 return false; 00448 } 00449 } 00450 00451 // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks. 00452 // We should instead work with the Revision object when we need it... 00453 $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed 00454 $this->mRevIdFetched = $this->mRevision->getId(); 00455 00456 wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); 00457 00458 wfProfileOut( __METHOD__ ); 00459 00460 return $this->mContentObject; 00461 } 00462 00467 public function forUpdate() { 00468 wfDeprecated( __METHOD__, '1.18' ); 00469 } 00470 00476 public function isCurrent() { 00477 # If no oldid, this is the current version. 00478 if ( $this->getOldID() == 0 ) { 00479 return true; 00480 } 00481 00482 return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent(); 00483 } 00484 00492 public function getRevisionFetched() { 00493 $this->fetchContentObject(); 00494 00495 return $this->mRevision; 00496 } 00497 00503 public function getRevIdFetched() { 00504 if ( $this->mRevIdFetched ) { 00505 return $this->mRevIdFetched; 00506 } else { 00507 return $this->mPage->getLatest(); 00508 } 00509 } 00510 00515 public function view() { 00516 global $wgUseFileCache, $wgUseETag, $wgDebugToolbar; 00517 00518 wfProfileIn( __METHOD__ ); 00519 00520 # Get variables from query string 00521 # As side effect this will load the revision and update the title 00522 # in a revision ID is passed in the request, so this should remain 00523 # the first call of this method even if $oldid is used way below. 00524 $oldid = $this->getOldID(); 00525 00526 $user = $this->getContext()->getUser(); 00527 # Another whitelist check in case getOldID() is altering the title 00528 $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user ); 00529 if ( count( $permErrors ) ) { 00530 wfDebug( __METHOD__ . ": denied on secondary read check\n" ); 00531 wfProfileOut( __METHOD__ ); 00532 throw new PermissionsError( 'read', $permErrors ); 00533 } 00534 00535 $outputPage = $this->getContext()->getOutput(); 00536 # getOldID() may as well want us to redirect somewhere else 00537 if ( $this->mRedirectUrl ) { 00538 $outputPage->redirect( $this->mRedirectUrl ); 00539 wfDebug( __METHOD__ . ": redirecting due to oldid\n" ); 00540 wfProfileOut( __METHOD__ ); 00541 00542 return; 00543 } 00544 00545 # If we got diff in the query, we want to see a diff page instead of the article. 00546 if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) { 00547 wfDebug( __METHOD__ . ": showing diff page\n" ); 00548 $this->showDiffPage(); 00549 wfProfileOut( __METHOD__ ); 00550 00551 return; 00552 } 00553 00554 # Set page title (may be overridden by DISPLAYTITLE) 00555 $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() ); 00556 00557 $outputPage->setArticleFlag( true ); 00558 # Allow frames by default 00559 $outputPage->allowClickjacking(); 00560 00561 $parserCache = ParserCache::singleton(); 00562 00563 $parserOptions = $this->getParserOptions(); 00564 # Render printable version, use printable version cache 00565 if ( $outputPage->isPrintable() ) { 00566 $parserOptions->setIsPrintable( true ); 00567 $parserOptions->setEditSection( false ); 00568 } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) { 00569 $parserOptions->setEditSection( false ); 00570 } 00571 00572 # Try client and file cache 00573 if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) { 00574 if ( $wgUseETag ) { 00575 $outputPage->setETag( $parserCache->getETag( $this, $parserOptions ) ); 00576 } 00577 00578 # Is it client cached? 00579 if ( $outputPage->checkLastModified( $this->mPage->getTouched() ) ) { 00580 wfDebug( __METHOD__ . ": done 304\n" ); 00581 wfProfileOut( __METHOD__ ); 00582 00583 return; 00584 # Try file cache 00585 } elseif ( $wgUseFileCache && $this->tryFileCache() ) { 00586 wfDebug( __METHOD__ . ": done file cache\n" ); 00587 # tell wgOut that output is taken care of 00588 $outputPage->disable(); 00589 $this->mPage->doViewUpdates( $user ); 00590 wfProfileOut( __METHOD__ ); 00591 00592 return; 00593 } 00594 } 00595 00596 # Should the parser cache be used? 00597 $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid ); 00598 wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" ); 00599 if ( $user->getStubThreshold() ) { 00600 wfIncrStats( 'pcache_miss_stub' ); 00601 } 00602 00603 $this->showRedirectedFromHeader(); 00604 $this->showNamespaceHeader(); 00605 00606 # Iterate through the possible ways of constructing the output text. 00607 # Keep going until $outputDone is set, or we run out of things to do. 00608 $pass = 0; 00609 $outputDone = false; 00610 $this->mParserOutput = false; 00611 00612 while ( !$outputDone && ++$pass ) { 00613 switch ( $pass ) { 00614 case 1: 00615 wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) ); 00616 break; 00617 case 2: 00618 # Early abort if the page doesn't exist 00619 if ( !$this->mPage->exists() ) { 00620 wfDebug( __METHOD__ . ": showing missing article\n" ); 00621 $this->showMissingArticle(); 00622 wfProfileOut( __METHOD__ ); 00623 return; 00624 } 00625 00626 # Try the parser cache 00627 if ( $useParserCache ) { 00628 $this->mParserOutput = $parserCache->get( $this, $parserOptions ); 00629 00630 if ( $this->mParserOutput !== false ) { 00631 if ( $oldid ) { 00632 wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" ); 00633 $this->setOldSubtitle( $oldid ); 00634 } else { 00635 wfDebug( __METHOD__ . ": showing parser cache contents\n" ); 00636 } 00637 $outputPage->addParserOutput( $this->mParserOutput ); 00638 # Ensure that UI elements requiring revision ID have 00639 # the correct version information. 00640 $outputPage->setRevisionId( $this->mPage->getLatest() ); 00641 # Preload timestamp to avoid a DB hit 00642 $cachedTimestamp = $this->mParserOutput->getTimestamp(); 00643 if ( $cachedTimestamp !== null ) { 00644 $outputPage->setRevisionTimestamp( $cachedTimestamp ); 00645 $this->mPage->setTimestamp( $cachedTimestamp ); 00646 } 00647 $outputDone = true; 00648 } 00649 } 00650 break; 00651 case 3: 00652 # This will set $this->mRevision if needed 00653 $this->fetchContentObject(); 00654 00655 # Are we looking at an old revision 00656 if ( $oldid && $this->mRevision ) { 00657 $this->setOldSubtitle( $oldid ); 00658 00659 if ( !$this->showDeletedRevisionHeader() ) { 00660 wfDebug( __METHOD__ . ": cannot view deleted revision\n" ); 00661 wfProfileOut( __METHOD__ ); 00662 return; 00663 } 00664 } 00665 00666 # Ensure that UI elements requiring revision ID have 00667 # the correct version information. 00668 $outputPage->setRevisionId( $this->getRevIdFetched() ); 00669 # Preload timestamp to avoid a DB hit 00670 $outputPage->setRevisionTimestamp( $this->getTimestamp() ); 00671 00672 # Pages containing custom CSS or JavaScript get special treatment 00673 if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) { 00674 wfDebug( __METHOD__ . ": showing CSS/JS source\n" ); 00675 $this->showCssOrJsPage(); 00676 $outputDone = true; 00677 } elseif ( !wfRunHooks( 'ArticleContentViewCustom', 00678 array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { 00679 00680 # Allow extensions do their own custom view for certain pages 00681 $outputDone = true; 00682 } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', 00683 array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { 00684 00685 # Allow extensions do their own custom view for certain pages 00686 $outputDone = true; 00687 } else { 00688 $content = $this->getContentObject(); 00689 $rt = $content ? $content->getRedirectChain() : null; 00690 if ( $rt ) { 00691 wfDebug( __METHOD__ . ": showing redirect=no page\n" ); 00692 # Viewing a redirect page (e.g. with parameter redirect=no) 00693 $outputPage->addHTML( $this->viewRedirect( $rt ) ); 00694 # Parse just to get categories, displaytitle, etc. 00695 $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false ); 00696 $outputPage->addParserOutputNoText( $this->mParserOutput ); 00697 $outputDone = true; 00698 } 00699 } 00700 break; 00701 case 4: 00702 # Run the parse, protected by a pool counter 00703 wfDebug( __METHOD__ . ": doing uncached parse\n" ); 00704 00705 $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions, 00706 $this->getRevIdFetched(), $useParserCache, $this->getContentObject() ); 00707 00708 if ( !$poolArticleView->execute() ) { 00709 $error = $poolArticleView->getError(); 00710 if ( $error ) { 00711 $outputPage->clearHTML(); // for release() errors 00712 $outputPage->enableClientCache( false ); 00713 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 00714 00715 $errortext = $error->getWikiText( false, 'view-pool-error' ); 00716 $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' ); 00717 } 00718 # Connection or timeout error 00719 wfProfileOut( __METHOD__ ); 00720 return; 00721 } 00722 00723 $this->mParserOutput = $poolArticleView->getParserOutput(); 00724 $outputPage->addParserOutput( $this->mParserOutput ); 00725 00726 # Don't cache a dirty ParserOutput object 00727 if ( $poolArticleView->getIsDirty() ) { 00728 $outputPage->setSquidMaxage( 0 ); 00729 $outputPage->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" ); 00730 } 00731 00732 $outputDone = true; 00733 break; 00734 # Should be unreachable, but just in case... 00735 default: 00736 break 2; 00737 } 00738 } 00739 00740 # Get the ParserOutput actually *displayed* here. 00741 # Note that $this->mParserOutput is the *current* version output. 00742 $pOutput = ( $outputDone instanceof ParserOutput ) 00743 ? $outputDone // object fetched by hook 00744 : $this->mParserOutput; 00745 00746 # Adjust title for main page & pages with displaytitle 00747 if ( $pOutput ) { 00748 $this->adjustDisplayTitle( $pOutput ); 00749 } 00750 00751 # For the main page, overwrite the <title> element with the con- 00752 # tents of 'pagetitle-view-mainpage' instead of the default (if 00753 # that's not empty). 00754 # This message always exists because it is in the i18n files 00755 if ( $this->getTitle()->isMainPage() ) { 00756 $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage(); 00757 if ( !$msg->isDisabled() ) { 00758 $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() ); 00759 } 00760 } 00761 00762 # Check for any __NOINDEX__ tags on the page using $pOutput 00763 $policy = $this->getRobotPolicy( 'view', $pOutput ); 00764 $outputPage->setIndexPolicy( $policy['index'] ); 00765 $outputPage->setFollowPolicy( $policy['follow'] ); 00766 00767 $this->showViewFooter(); 00768 $this->mPage->doViewUpdates( $user ); 00769 00770 $outputPage->addModules( 'mediawiki.action.view.postEdit' ); 00771 00772 wfProfileOut( __METHOD__ ); 00773 } 00774 00779 public function adjustDisplayTitle( ParserOutput $pOutput ) { 00780 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion 00781 $titleText = $pOutput->getTitleText(); 00782 if ( strval( $titleText ) !== '' ) { 00783 $this->getContext()->getOutput()->setPageTitle( $titleText ); 00784 } 00785 } 00786 00793 public function showDiffPage() { 00794 $request = $this->getContext()->getRequest(); 00795 $user = $this->getContext()->getUser(); 00796 $diff = $request->getVal( 'diff' ); 00797 $rcid = $request->getVal( 'rcid' ); 00798 $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) ); 00799 $purge = $request->getVal( 'action' ) == 'purge'; 00800 $unhide = $request->getInt( 'unhide' ) == 1; 00801 $oldid = $this->getOldID(); 00802 00803 $rev = $this->getRevisionFetched(); 00804 00805 if ( !$rev ) { 00806 $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) ); 00807 $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 ); 00808 return; 00809 } 00810 00811 $contentHandler = $rev->getContentHandler(); 00812 $de = $contentHandler->createDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide ); 00813 00814 // DifferenceEngine directly fetched the revision: 00815 $this->mRevIdFetched = $de->mNewid; 00816 $de->showDiffPage( $diffOnly ); 00817 00818 if ( $diff == 0 || $diff == $this->mPage->getLatest() ) { 00819 # Run view updates for current revision only 00820 $this->mPage->doViewUpdates( $user ); 00821 } 00822 } 00823 00833 protected function showCssOrJsPage( $showCacheHint = true ) { 00834 $outputPage = $this->getContext()->getOutput(); 00835 00836 if ( $showCacheHint ) { 00837 $dir = $this->getContext()->getLanguage()->getDir(); 00838 $lang = $this->getContext()->getLanguage()->getCode(); 00839 00840 $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>", 00841 'clearyourcache' ); 00842 } 00843 00844 $this->fetchContentObject(); 00845 00846 if ( $this->mContentObject ) { 00847 // Give hooks a chance to customise the output 00848 if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mContentObject, $this->getTitle(), $outputPage ) ) ) { 00849 $po = $this->mContentObject->getParserOutput( $this->getTitle() ); 00850 $outputPage->addHTML( $po->getText() ); 00851 } 00852 } 00853 } 00854 00862 public function getRobotPolicy( $action, $pOutput = null ) { 00863 global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy; 00864 00865 $ns = $this->getTitle()->getNamespace(); 00866 00867 # Don't index user and user talk pages for blocked users (bug 11443) 00868 if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) { 00869 $specificTarget = null; 00870 $vagueTarget = null; 00871 $titleText = $this->getTitle()->getText(); 00872 if ( IP::isValid( $titleText ) ) { 00873 $vagueTarget = $titleText; 00874 } else { 00875 $specificTarget = $titleText; 00876 } 00877 if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) { 00878 return array( 00879 'index' => 'noindex', 00880 'follow' => 'nofollow' 00881 ); 00882 } 00883 } 00884 00885 if ( $this->mPage->getID() === 0 || $this->getOldID() ) { 00886 # Non-articles (special pages etc), and old revisions 00887 return array( 00888 'index' => 'noindex', 00889 'follow' => 'nofollow' 00890 ); 00891 } elseif ( $this->getContext()->getOutput()->isPrintable() ) { 00892 # Discourage indexing of printable versions, but encourage following 00893 return array( 00894 'index' => 'noindex', 00895 'follow' => 'follow' 00896 ); 00897 } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) { 00898 # For ?curid=x urls, disallow indexing 00899 return array( 00900 'index' => 'noindex', 00901 'follow' => 'follow' 00902 ); 00903 } 00904 00905 # Otherwise, construct the policy based on the various config variables. 00906 $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy ); 00907 00908 if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) { 00909 # Honour customised robot policies for this namespace 00910 $policy = array_merge( 00911 $policy, 00912 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] ) 00913 ); 00914 } 00915 if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) { 00916 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates 00917 # a final sanity check that we have really got the parser output. 00918 $policy = array_merge( 00919 $policy, 00920 array( 'index' => $pOutput->getIndexPolicy() ) 00921 ); 00922 } 00923 00924 if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) { 00925 # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__ 00926 $policy = array_merge( 00927 $policy, 00928 self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) 00929 ); 00930 } 00931 00932 return $policy; 00933 } 00934 00942 public static function formatRobotPolicy( $policy ) { 00943 if ( is_array( $policy ) ) { 00944 return $policy; 00945 } elseif ( !$policy ) { 00946 return array(); 00947 } 00948 00949 $policy = explode( ',', $policy ); 00950 $policy = array_map( 'trim', $policy ); 00951 00952 $arr = array(); 00953 foreach ( $policy as $var ) { 00954 if ( in_array( $var, array( 'index', 'noindex' ) ) ) { 00955 $arr['index'] = $var; 00956 } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) { 00957 $arr['follow'] = $var; 00958 } 00959 } 00960 00961 return $arr; 00962 } 00963 00971 public function showRedirectedFromHeader() { 00972 global $wgRedirectSources; 00973 $outputPage = $this->getContext()->getOutput(); 00974 00975 $rdfrom = $this->getContext()->getRequest()->getVal( 'rdfrom' ); 00976 00977 if ( isset( $this->mRedirectedFrom ) ) { 00978 // This is an internally redirected page view. 00979 // We'll need a backlink to the source page for navigation. 00980 if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) { 00981 $redir = Linker::linkKnown( 00982 $this->mRedirectedFrom, 00983 null, 00984 array(), 00985 array( 'redirect' => 'no' ) 00986 ); 00987 00988 $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) ); 00989 00990 // Set the fragment if one was specified in the redirect 00991 if ( strval( $this->getTitle()->getFragment() ) != '' ) { 00992 $outputPage->addInlineScript( Xml::encodeJsCall( 00993 'redirectToFragment', array( $this->getTitle()->getFragmentForURL() ) 00994 ) ); 00995 } 00996 00997 // Add a <link rel="canonical"> tag 00998 $outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() ); 00999 01000 // Tell the output object that the user arrived at this article through a redirect 01001 $outputPage->setRedirectedFrom( $this->mRedirectedFrom ); 01002 01003 return true; 01004 } 01005 } elseif ( $rdfrom ) { 01006 // This is an externally redirected view, from some other wiki. 01007 // If it was reported from a trusted site, supply a backlink. 01008 if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { 01009 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom ); 01010 $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) ); 01011 01012 return true; 01013 } 01014 } 01015 01016 return false; 01017 } 01018 01023 public function showNamespaceHeader() { 01024 if ( $this->getTitle()->isTalkPage() ) { 01025 if ( !wfMessage( 'talkpageheader' )->isDisabled() ) { 01026 $this->getContext()->getOutput()->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1\n</div>", array( 'talkpageheader' ) ); 01027 } 01028 } 01029 } 01030 01034 public function showViewFooter() { 01035 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page 01036 if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) { 01037 $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' ); 01038 } 01039 01040 // Show a footer allowing the user to patrol the shown revision or page if possible 01041 $patrolFooterShown = $this->showPatrolFooter(); 01042 01043 wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) ); 01044 01045 } 01046 01056 public function showPatrolFooter() { 01057 global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI; 01058 01059 $outputPage = $this->getContext()->getOutput(); 01060 $user = $this->getContext()->getUser(); 01061 $cache = wfGetMainCache(); 01062 $rc = false; 01063 01064 if ( !$this->getTitle()->quickUserCan( 'patrol', $user ) || !( $wgUseRCPatrol || $wgUseNPPatrol ) ) { 01065 // Patrolling is disabled or the user isn't allowed to 01066 return false; 01067 } 01068 01069 wfProfileIn( __METHOD__ ); 01070 01071 // New page patrol: Get the timestamp of the oldest revison which 01072 // the revision table holds for the given page. Then we look 01073 // whether it's within the RC lifespan and if it is, we try 01074 // to get the recentchanges row belonging to that entry 01075 // (with rc_new = 1). 01076 01077 // Check for cached results 01078 if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) { 01079 wfProfileOut( __METHOD__ ); 01080 return false; 01081 } 01082 01083 if ( $this->mRevision && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 ) ) { 01084 // The current revision is already older than what could be in the RC table 01085 // 6h tolerance because the RC might not be cleaned out regularly 01086 wfProfileOut( __METHOD__ ); 01087 return false; 01088 } 01089 01090 $dbr = wfGetDB( DB_SLAVE ); 01091 $oldestRevisionTimestamp = $dbr->selectField( 01092 'revision', 01093 'MIN( rev_timestamp )', 01094 array( 'rev_page' => $this->getTitle()->getArticleID() ), 01095 __METHOD__ 01096 ); 01097 01098 if ( $oldestRevisionTimestamp && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 ) ) { 01099 // 6h tolerance because the RC might not be cleaned out regularly 01100 $rc = RecentChange::newFromConds( 01101 array( 01102 'rc_new' => 1, 01103 'rc_timestamp' => $oldestRevisionTimestamp, 01104 'rc_namespace' => $this->getTitle()->getNamespace(), 01105 'rc_cur_id' => $this->getTitle()->getArticleID(), 01106 'rc_patrolled' => 0 01107 ), 01108 __METHOD__, 01109 array( 'USE INDEX' => 'new_name_timestamp' ) 01110 ); 01111 } 01112 01113 if ( !$rc ) { 01114 // No RC entry around 01115 01116 // Cache the information we gathered above in case we can't patrol 01117 // Don't cache in case we can patrol as this could change 01118 $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' ); 01119 01120 wfProfileOut( __METHOD__ ); 01121 return false; 01122 } 01123 01124 if ( $rc->getPerformer()->getName() == $user->getName() ) { 01125 // Don't show a patrol link for own creations. If the user could 01126 // patrol them, they already would be patrolled 01127 wfProfileOut( __METHOD__ ); 01128 return false; 01129 } 01130 01131 $rcid = $rc->getAttribute( 'rc_id' ); 01132 01133 $token = $user->getEditToken( $rcid ); 01134 01135 $outputPage->preventClickjacking(); 01136 if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) { 01137 $outputPage->addModules( 'mediawiki.page.patrol.ajax' ); 01138 } 01139 01140 $link = Linker::linkKnown( 01141 $this->getTitle(), 01142 wfMessage( 'markaspatrolledtext' )->escaped(), 01143 array(), 01144 array( 01145 'action' => 'markpatrolled', 01146 'rcid' => $rcid, 01147 'token' => $token, 01148 ) 01149 ); 01150 01151 $outputPage->addHTML( 01152 "<div class='patrollink'>" . 01153 wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() . 01154 '</div>' 01155 ); 01156 01157 wfProfileOut( __METHOD__ ); 01158 return true; 01159 } 01160 01165 public function showMissingArticle() { 01166 global $wgSend404Code; 01167 $outputPage = $this->getContext()->getOutput(); 01168 // Whether the page is a root user page of an existing user (but not a subpage) 01169 $validUserPage = false; 01170 01171 # Show info in user (talk) namespace. Does the user exist? Is he blocked? 01172 if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) { 01173 $parts = explode( '/', $this->getTitle()->getText() ); 01174 $rootPart = $parts[0]; 01175 $user = User::newFromName( $rootPart, false /* allow IP users*/ ); 01176 $ip = User::isIP( $rootPart ); 01177 01178 if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist 01179 $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>", 01180 array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) ); 01181 } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked 01182 LogEventsList::showLogExtract( 01183 $outputPage, 01184 'block', 01185 $user->getUserPage(), 01186 '', 01187 array( 01188 'lim' => 1, 01189 'showIfEmpty' => false, 01190 'msgKey' => array( 01191 'blocked-notice-logextract', 01192 $user->getName() # Support GENDER in notice 01193 ) 01194 ) 01195 ); 01196 $validUserPage = !$this->getTitle()->isSubpage(); 01197 } else { 01198 $validUserPage = !$this->getTitle()->isSubpage(); 01199 } 01200 } 01201 01202 wfRunHooks( 'ShowMissingArticle', array( $this ) ); 01203 01204 # Show delete and move logs 01205 LogEventsList::showLogExtract( $outputPage, array( 'delete', 'move' ), $this->getTitle(), '', 01206 array( 'lim' => 10, 01207 'conds' => array( "log_action != 'revision'" ), 01208 'showIfEmpty' => false, 01209 'msgKey' => array( 'moveddeleted-notice' ) ) 01210 ); 01211 01212 if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) { 01213 // If there's no backing content, send a 404 Not Found 01214 // for better machine handling of broken links. 01215 $this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" ); 01216 } 01217 01218 if ( $validUserPage ) { 01219 // Also apply the robot policy for nonexisting user pages (as those aren't served as 404) 01220 $policy = $this->getRobotPolicy( 'view' ); 01221 $outputPage->setIndexPolicy( $policy['index'] ); 01222 $outputPage->setFollowPolicy( $policy['follow'] ); 01223 } 01224 01225 $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) ); 01226 01227 if ( ! $hookResult ) { 01228 return; 01229 } 01230 01231 # Show error message 01232 $oldid = $this->getOldID(); 01233 if ( $oldid ) { 01234 $text = wfMessage( 'missing-revision', $oldid )->plain(); 01235 } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) { 01236 // Use the default message text 01237 $text = $this->getTitle()->getDefaultMessageText(); 01238 } elseif ( $this->getTitle()->quickUserCan( 'create', $this->getContext()->getUser() ) 01239 && $this->getTitle()->quickUserCan( 'edit', $this->getContext()->getUser() ) 01240 ) { 01241 $text = wfMessage( 'noarticletext' )->plain(); 01242 } else { 01243 $text = wfMessage( 'noarticletext-nopermission' )->plain(); 01244 } 01245 $text = "<div class='noarticletext'>\n$text\n</div>"; 01246 01247 $outputPage->addWikiText( $text ); 01248 } 01249 01256 public function showDeletedRevisionHeader() { 01257 if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { 01258 // Not deleted 01259 return true; 01260 } 01261 01262 $outputPage = $this->getContext()->getOutput(); 01263 $user = $this->getContext()->getUser(); 01264 // If the user is not allowed to see it... 01265 if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) { 01266 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 01267 'rev-deleted-text-permission' ); 01268 01269 return false; 01270 // If the user needs to confirm that they want to see it... 01271 } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) { 01272 # Give explanation and add a link to view the revision... 01273 $oldid = intval( $this->getOldID() ); 01274 $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" ); 01275 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ? 01276 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide'; 01277 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 01278 array( $msg, $link ) ); 01279 01280 return false; 01281 // We are allowed to see... 01282 } else { 01283 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ? 01284 'rev-suppressed-text-view' : 'rev-deleted-text-view'; 01285 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg ); 01286 01287 return true; 01288 } 01289 } 01290 01299 public function setOldSubtitle( $oldid = 0 ) { 01300 if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) { 01301 return; 01302 } 01303 01304 $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1; 01305 01306 # Cascade unhide param in links for easy deletion browsing 01307 $extraParams = array(); 01308 if ( $unhide ) { 01309 $extraParams['unhide'] = 1; 01310 } 01311 01312 if ( $this->mRevision && $this->mRevision->getId() === $oldid ) { 01313 $revision = $this->mRevision; 01314 } else { 01315 $revision = Revision::newFromId( $oldid ); 01316 } 01317 01318 $timestamp = $revision->getTimestamp(); 01319 01320 $current = ( $oldid == $this->mPage->getLatest() ); 01321 $language = $this->getContext()->getLanguage(); 01322 $user = $this->getContext()->getUser(); 01323 01324 $td = $language->userTimeAndDate( $timestamp, $user ); 01325 $tddate = $language->userDate( $timestamp, $user ); 01326 $tdtime = $language->userTime( $timestamp, $user ); 01327 01328 # Show user links if allowed to see them. If hidden, then show them only if requested... 01329 $userlinks = Linker::revUserTools( $revision, !$unhide ); 01330 01331 $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled() 01332 ? 'revision-info-current' 01333 : 'revision-info'; 01334 01335 $outputPage = $this->getContext()->getOutput(); 01336 $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg, 01337 $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate, 01338 $tdtime, $revision->getUser() )->parse() . "</div>" ); 01339 01340 $lnk = $current 01341 ? wfMessage( 'currentrevisionlink' )->escaped() 01342 : Linker::linkKnown( 01343 $this->getTitle(), 01344 wfMessage( 'currentrevisionlink' )->escaped(), 01345 array(), 01346 $extraParams 01347 ); 01348 $curdiff = $current 01349 ? wfMessage( 'diff' )->escaped() 01350 : Linker::linkKnown( 01351 $this->getTitle(), 01352 wfMessage( 'diff' )->escaped(), 01353 array(), 01354 array( 01355 'diff' => 'cur', 01356 'oldid' => $oldid 01357 ) + $extraParams 01358 ); 01359 $prev = $this->getTitle()->getPreviousRevisionID( $oldid ); 01360 $prevlink = $prev 01361 ? Linker::linkKnown( 01362 $this->getTitle(), 01363 wfMessage( 'previousrevision' )->escaped(), 01364 array(), 01365 array( 01366 'direction' => 'prev', 01367 'oldid' => $oldid 01368 ) + $extraParams 01369 ) 01370 : wfMessage( 'previousrevision' )->escaped(); 01371 $prevdiff = $prev 01372 ? Linker::linkKnown( 01373 $this->getTitle(), 01374 wfMessage( 'diff' )->escaped(), 01375 array(), 01376 array( 01377 'diff' => 'prev', 01378 'oldid' => $oldid 01379 ) + $extraParams 01380 ) 01381 : wfMessage( 'diff' )->escaped(); 01382 $nextlink = $current 01383 ? wfMessage( 'nextrevision' )->escaped() 01384 : Linker::linkKnown( 01385 $this->getTitle(), 01386 wfMessage( 'nextrevision' )->escaped(), 01387 array(), 01388 array( 01389 'direction' => 'next', 01390 'oldid' => $oldid 01391 ) + $extraParams 01392 ); 01393 $nextdiff = $current 01394 ? wfMessage( 'diff' )->escaped() 01395 : Linker::linkKnown( 01396 $this->getTitle(), 01397 wfMessage( 'diff' )->escaped(), 01398 array(), 01399 array( 01400 'diff' => 'next', 01401 'oldid' => $oldid 01402 ) + $extraParams 01403 ); 01404 01405 $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() ); 01406 if ( $cdel !== '' ) { 01407 $cdel .= ' '; 01408 } 01409 01410 $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel . 01411 wfMessage( 'revision-nav' )->rawParams( 01412 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff 01413 )->escaped() . "</div>" ); 01414 } 01415 01424 public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) { 01425 global $wgStylePath; 01426 01427 if ( !is_array( $target ) ) { 01428 $target = array( $target ); 01429 } 01430 01431 $lang = $this->getTitle()->getPageLanguage(); 01432 $imageDir = $lang->getDir(); 01433 01434 if ( $appendSubtitle ) { 01435 $out = $this->getContext()->getOutput(); 01436 $out->addSubtitle( wfMessage( 'redirectpagesub' )->escaped() ); 01437 } 01438 01439 // the loop prepends the arrow image before the link, so the first case needs to be outside 01440 01444 $title = array_shift( $target ); 01445 01446 if ( $forceKnown ) { 01447 $link = Linker::linkKnown( $title, htmlspecialchars( $title->getFullText() ) ); 01448 } else { 01449 $link = Linker::link( $title, htmlspecialchars( $title->getFullText() ) ); 01450 } 01451 01452 $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png'; 01453 $alt = $lang->isRTL() ? '←' : '→'; 01454 // Automatically append redirect=no to each link, since most of them are redirect pages themselves. 01455 foreach ( $target as $rt ) { 01456 $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) ); 01457 if ( $forceKnown ) { 01458 $link .= Linker::linkKnown( $rt, htmlspecialchars( $rt->getFullText(), array(), array( 'redirect' => 'no' ) ) ); 01459 } else { 01460 $link .= Linker::link( $rt, htmlspecialchars( $rt->getFullText() ), array(), array( 'redirect' => 'no' ) ); 01461 } 01462 } 01463 01464 $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png'; 01465 return '<div class="redirectMsg">' . 01466 Html::element( 'img', array( 'src' => $imageUrl, 'alt' => '#REDIRECT' ) ) . 01467 '<span class="redirectText">' . $link . '</span></div>'; 01468 } 01469 01473 public function render() { 01474 $this->getContext()->getOutput()->setArticleBodyOnly( true ); 01475 $this->view(); 01476 } 01477 01481 public function protect() { 01482 $form = new ProtectionForm( $this ); 01483 $form->execute(); 01484 } 01485 01489 public function unprotect() { 01490 $this->protect(); 01491 } 01492 01496 public function delete() { 01497 # This code desperately needs to be totally rewritten 01498 01499 $title = $this->getTitle(); 01500 $user = $this->getContext()->getUser(); 01501 01502 # Check permissions 01503 $permission_errors = $title->getUserPermissionsErrors( 'delete', $user ); 01504 if ( count( $permission_errors ) ) { 01505 throw new PermissionsError( 'delete', $permission_errors ); 01506 } 01507 01508 # Read-only check... 01509 if ( wfReadOnly() ) { 01510 throw new ReadOnlyError; 01511 } 01512 01513 # Better double-check that it hasn't been deleted yet! 01514 $this->mPage->loadPageData( 'fromdbmaster' ); 01515 if ( !$this->mPage->exists() ) { 01516 $deleteLogPage = new LogPage( 'delete' ); 01517 $outputPage = $this->getContext()->getOutput(); 01518 $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) ); 01519 $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>", 01520 array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ) 01521 ); 01522 $outputPage->addHTML( 01523 Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) 01524 ); 01525 LogEventsList::showLogExtract( 01526 $outputPage, 01527 'delete', 01528 $title 01529 ); 01530 01531 return; 01532 } 01533 01534 $request = $this->getContext()->getRequest(); 01535 $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' ); 01536 $deleteReason = $request->getText( 'wpReason' ); 01537 01538 if ( $deleteReasonList == 'other' ) { 01539 $reason = $deleteReason; 01540 } elseif ( $deleteReason != '' ) { 01541 // Entry from drop down menu + additional comment 01542 $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text(); 01543 $reason = $deleteReasonList . $colonseparator . $deleteReason; 01544 } else { 01545 $reason = $deleteReasonList; 01546 } 01547 01548 if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ), 01549 array( 'delete', $this->getTitle()->getPrefixedText() ) ) ) 01550 { 01551 # Flag to hide all contents of the archived revisions 01552 $suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' ); 01553 01554 $this->doDelete( $reason, $suppress ); 01555 01556 WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user ); 01557 01558 return; 01559 } 01560 01561 // Generate deletion reason 01562 $hasHistory = false; 01563 if ( !$reason ) { 01564 try { 01565 $reason = $this->generateReason( $hasHistory ); 01566 } catch ( MWException $e ) { 01567 # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here. 01568 wfDebug( "Error while building auto delete summary: $e" ); 01569 $reason = ''; 01570 } 01571 } 01572 01573 // If the page has a history, insert a warning 01574 if ( $hasHistory ) { 01575 $revisions = $this->mTitle->estimateRevisionCount(); 01576 // @todo FIXME: i18n issue/patchwork message 01577 $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' . 01578 wfMessage( 'historywarning' )->numParams( $revisions )->parse() . 01579 wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title, 01580 wfMessage( 'history' )->escaped(), 01581 array( 'rel' => 'archives' ), 01582 array( 'action' => 'history' ) ) . 01583 '</strong>' 01584 ); 01585 01586 if ( $this->mTitle->isBigDeletion() ) { 01587 global $wgDeleteRevisionsLimit; 01588 $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", 01589 array( 'delete-warning-toobig', $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit ) ) ); 01590 } 01591 } 01592 01593 $this->confirmDelete( $reason ); 01594 } 01595 01601 public function confirmDelete( $reason ) { 01602 wfDebug( "Article::confirmDelete\n" ); 01603 01604 $outputPage = $this->getContext()->getOutput(); 01605 $outputPage->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) ); 01606 $outputPage->addBacklinkSubtitle( $this->getTitle() ); 01607 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 01608 $outputPage->addWikiMsg( 'confirmdeletetext' ); 01609 01610 wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) ); 01611 01612 $user = $this->getContext()->getUser(); 01613 01614 if ( $user->isAllowed( 'suppressrevision' ) ) { 01615 $suppress = "<tr id=\"wpDeleteSuppressRow\"> 01616 <td></td> 01617 <td class='mw-input'><strong>" . 01618 Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(), 01619 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) . 01620 "</strong></td> 01621 </tr>"; 01622 } else { 01623 $suppress = ''; 01624 } 01625 $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $this->getTitle() ); 01626 01627 $form = Xml::openElement( 'form', array( 'method' => 'post', 01628 'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) . 01629 Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) . 01630 Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) . 01631 Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) . 01632 "<tr id=\"wpDeleteReasonListRow\"> 01633 <td class='mw-label'>" . 01634 Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) . 01635 "</td> 01636 <td class='mw-input'>" . 01637 Xml::listDropDown( 'wpDeleteReasonList', 01638 wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(), 01639 wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(), '', 'wpReasonDropDown', 1 ) . 01640 "</td> 01641 </tr> 01642 <tr id=\"wpDeleteReasonRow\"> 01643 <td class='mw-label'>" . 01644 Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) . 01645 "</td> 01646 <td class='mw-input'>" . 01647 Html::input( 'wpReason', $reason, 'text', array( 01648 'size' => '60', 01649 'maxlength' => '255', 01650 'tabindex' => '2', 01651 'id' => 'wpReason', 01652 'autofocus' 01653 ) ) . 01654 "</td> 01655 </tr>"; 01656 01657 # Disallow watching if user is not logged in 01658 if ( $user->isLoggedIn() ) { 01659 $form .= " 01660 <tr> 01661 <td></td> 01662 <td class='mw-input'>" . 01663 Xml::checkLabel( wfMessage( 'watchthis' )->text(), 01664 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) . 01665 "</td> 01666 </tr>"; 01667 } 01668 01669 $form .= " 01670 $suppress 01671 <tr> 01672 <td></td> 01673 <td class='mw-submit'>" . 01674 Xml::submitButton( wfMessage( 'deletepage' )->text(), 01675 array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) . 01676 "</td> 01677 </tr>" . 01678 Xml::closeElement( 'table' ) . 01679 Xml::closeElement( 'fieldset' ) . 01680 Html::hidden( 'wpEditToken', $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) ) ) . 01681 Xml::closeElement( 'form' ); 01682 01683 if ( $user->isAllowed( 'editinterface' ) ) { 01684 $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' ); 01685 $link = Linker::link( 01686 $title, 01687 wfMessage( 'delete-edit-reasonlist' )->escaped(), 01688 array(), 01689 array( 'action' => 'edit' ) 01690 ); 01691 $form .= '<p class="mw-delete-editreasons">' . $link . '</p>'; 01692 } 01693 01694 $outputPage->addHTML( $form ); 01695 01696 $deleteLogPage = new LogPage( 'delete' ); 01697 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) ); 01698 LogEventsList::showLogExtract( $outputPage, 'delete', 01699 $this->getTitle() 01700 ); 01701 } 01702 01708 public function doDelete( $reason, $suppress = false ) { 01709 $error = ''; 01710 $outputPage = $this->getContext()->getOutput(); 01711 $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error ); 01712 if ( $status->isGood() ) { 01713 $deleted = $this->getTitle()->getPrefixedText(); 01714 01715 $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) ); 01716 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 01717 01718 $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]'; 01719 01720 $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink ); 01721 $outputPage->returnToMain( false ); 01722 } else { 01723 $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $this->getTitle()->getPrefixedText() ) ); 01724 if ( $error == '' ) { 01725 $outputPage->addWikiText( 01726 "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>" 01727 ); 01728 $deleteLogPage = new LogPage( 'delete' ); 01729 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) ); 01730 01731 LogEventsList::showLogExtract( 01732 $outputPage, 01733 'delete', 01734 $this->getTitle() 01735 ); 01736 } else { 01737 $outputPage->addHTML( $error ); 01738 } 01739 } 01740 } 01741 01742 /* Caching functions */ 01743 01751 protected function tryFileCache() { 01752 static $called = false; 01753 01754 if ( $called ) { 01755 wfDebug( "Article::tryFileCache(): called twice!?\n" ); 01756 return false; 01757 } 01758 01759 $called = true; 01760 if ( $this->isFileCacheable() ) { 01761 $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'view' ); 01762 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) { 01763 wfDebug( "Article::tryFileCache(): about to load file\n" ); 01764 $cache->loadFromFileCache( $this->getContext() ); 01765 return true; 01766 } else { 01767 wfDebug( "Article::tryFileCache(): starting buffer\n" ); 01768 ob_start( array( &$cache, 'saveToFileCache' ) ); 01769 } 01770 } else { 01771 wfDebug( "Article::tryFileCache(): not cacheable\n" ); 01772 } 01773 01774 return false; 01775 } 01776 01781 public function isFileCacheable() { 01782 $cacheable = false; 01783 01784 if ( HTMLFileCache::useFileCache( $this->getContext() ) ) { 01785 $cacheable = $this->mPage->getID() 01786 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect(); 01787 // Extension may have reason to disable file caching on some pages. 01788 if ( $cacheable ) { 01789 $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) ); 01790 } 01791 } 01792 01793 return $cacheable; 01794 } 01795 01809 public function getParserOutput( $oldid = null, User $user = null ) { 01810 //XXX: bypasses mParserOptions and thus setParserOptions() 01811 01812 if ( $user === null ) { 01813 $parserOptions = $this->getParserOptions(); 01814 } else { 01815 $parserOptions = $this->mPage->makeParserOptions( $user ); 01816 } 01817 01818 return $this->mPage->getParserOutput( $parserOptions, $oldid ); 01819 } 01820 01827 public function setParserOptions( ParserOptions $options ) { 01828 if ( $this->mParserOptions ) { 01829 throw new MWException( "can't change parser options after they have already been set" ); 01830 } 01831 01832 // clone, so if $options is modified later, it doesn't confuse the parser cache. 01833 $this->mParserOptions = clone $options; 01834 } 01835 01840 public function getParserOptions() { 01841 if ( !$this->mParserOptions ) { 01842 $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() ); 01843 } 01844 // Clone to allow modifications of the return value without affecting cache 01845 return clone $this->mParserOptions; 01846 } 01847 01854 public function setContext( $context ) { 01855 $this->mContext = $context; 01856 } 01857 01864 public function getContext() { 01865 if ( $this->mContext instanceof IContextSource ) { 01866 return $this->mContext; 01867 } else { 01868 wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" ); 01869 return RequestContext::getMain(); 01870 } 01871 } 01872 01877 public function info() { 01878 wfDeprecated( __METHOD__, '1.19' ); 01879 Action::factory( 'info', $this )->show(); 01880 } 01881 01886 public function markpatrolled() { 01887 wfDeprecated( __METHOD__, '1.18' ); 01888 Action::factory( 'markpatrolled', $this )->show(); 01889 } 01890 01896 public function purge() { 01897 return Action::factory( 'purge', $this )->show(); 01898 } 01899 01904 public function revert() { 01905 wfDeprecated( __METHOD__, '1.19' ); 01906 Action::factory( 'revert', $this )->show(); 01907 } 01908 01913 public function rollback() { 01914 wfDeprecated( __METHOD__, '1.19' ); 01915 Action::factory( 'rollback', $this )->show(); 01916 } 01917 01923 public function watch() { 01924 wfDeprecated( __METHOD__, '1.18' ); 01925 Action::factory( 'watch', $this )->show(); 01926 } 01927 01936 public function doWatch() { 01937 wfDeprecated( __METHOD__, '1.18' ); 01938 return WatchAction::doWatch( $this->getTitle(), $this->getContext()->getUser() ); 01939 } 01940 01946 public function unwatch() { 01947 wfDeprecated( __METHOD__, '1.18' ); 01948 Action::factory( 'unwatch', $this )->show(); 01949 } 01950 01956 public function doUnwatch() { 01957 wfDeprecated( __METHOD__, '1.18' ); 01958 return WatchAction::doUnwatch( $this->getTitle(), $this->getContext()->getUser() ); 01959 } 01960 01970 public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) { 01971 wfDeprecated( __METHOD__, '1.18' ); 01972 if ( $noRedir ) { 01973 $query = 'redirect=no'; 01974 if ( $extraQuery ) { 01975 $query .= "&$extraQuery"; 01976 } 01977 } else { 01978 $query = $extraQuery; 01979 } 01980 01981 $this->getContext()->getOutput()->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor ); 01982 } 01983 01990 public function __get( $fname ) { 01991 if ( property_exists( $this->mPage, $fname ) ) { 01992 #wfWarn( "Access to raw $fname field " . __CLASS__ ); 01993 return $this->mPage->$fname; 01994 } 01995 trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE ); 01996 } 01997 02005 public function __set( $fname, $fvalue ) { 02006 if ( property_exists( $this->mPage, $fname ) ) { 02007 #wfWarn( "Access to raw $fname field of " . __CLASS__ ); 02008 $this->mPage->$fname = $fvalue; 02009 // Note: extensions may want to toss on new fields 02010 } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) { 02011 $this->mPage->$fname = $fvalue; 02012 } else { 02013 trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE ); 02014 } 02015 } 02016 02025 public function __call( $fname, $args ) { 02026 if ( is_callable( array( $this->mPage, $fname ) ) ) { 02027 #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" ); 02028 return call_user_func_array( array( $this->mPage, $fname ), $args ); 02029 } 02030 trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR ); 02031 } 02032 02033 // ****** B/C functions to work-around PHP silliness with __call and references ****** // 02034 02043 public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) { 02044 return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user ); 02045 } 02046 02054 public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) { 02055 return $this->mPage->doUpdateRestrictions( 02056 $limit, 02057 $expiry, 02058 $cascade, 02059 $reason, 02060 $this->getContext()->getUser() 02061 ); 02062 } 02063 02072 public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) { 02073 return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error ); 02074 } 02075 02085 public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) { 02086 $user = is_null( $user ) ? $this->getContext()->getUser() : $user; 02087 return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user ); 02088 } 02089 02098 public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) { 02099 $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser; 02100 return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser ); 02101 } 02102 02107 public function generateReason( &$hasHistory ) { 02108 $title = $this->mPage->getTitle(); 02109 $handler = ContentHandler::getForTitle( $title ); 02110 return $handler->getAutoDeleteReason( $title, $hasHistory ); 02111 } 02112 02113 // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** // 02114 02118 public static function selectFields() { 02119 return WikiPage::selectFields(); 02120 } 02121 02125 public static function onArticleCreate( $title ) { 02126 WikiPage::onArticleCreate( $title ); 02127 } 02128 02132 public static function onArticleDelete( $title ) { 02133 WikiPage::onArticleDelete( $title ); 02134 } 02135 02139 public static function onArticleEdit( $title ) { 02140 WikiPage::onArticleEdit( $title ); 02141 } 02142 02150 public static function getAutosummary( $oldtext, $newtext, $flags ) { 02151 return WikiPage::getAutosummary( $oldtext, $newtext, $flags ); 02152 } 02153 // ****** 02154 }