MediaWiki
REL1_21
|
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 $this->mContent = ContentHandler::getContentText( $content ); #@todo: get rid of mContent everywhere! 00389 ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); 00390 00391 wfProfileOut( __METHOD__ ); 00392 00393 return $this->mContent; 00394 } 00395 00407 protected function fetchContentObject() { 00408 if ( $this->mContentLoaded ) { 00409 return $this->mContentObject; 00410 } 00411 00412 wfProfileIn( __METHOD__ ); 00413 00414 $this->mContentLoaded = true; 00415 $this->mContent = null; 00416 00417 $oldid = $this->getOldID(); 00418 00419 # Pre-fill content with error message so that if something 00420 # fails we'll have something telling us what we intended. 00421 //XXX: this isn't page content but a UI message. horrible. 00422 $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() ); 00423 00424 if ( $oldid ) { 00425 # $this->mRevision might already be fetched by getOldIDFromRequest() 00426 if ( !$this->mRevision ) { 00427 $this->mRevision = Revision::newFromId( $oldid ); 00428 if ( !$this->mRevision ) { 00429 wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" ); 00430 wfProfileOut( __METHOD__ ); 00431 return false; 00432 } 00433 } 00434 } else { 00435 if ( !$this->mPage->getLatest() ) { 00436 wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" ); 00437 wfProfileOut( __METHOD__ ); 00438 return false; 00439 } 00440 00441 $this->mRevision = $this->mPage->getRevision(); 00442 00443 if ( !$this->mRevision ) { 00444 wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" ); 00445 wfProfileOut( __METHOD__ ); 00446 return false; 00447 } 00448 } 00449 00450 // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks. 00451 // We should instead work with the Revision object when we need it... 00452 $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed 00453 $this->mRevIdFetched = $this->mRevision->getId(); 00454 00455 wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); 00456 00457 wfProfileOut( __METHOD__ ); 00458 00459 return $this->mContentObject; 00460 } 00461 00466 public function forUpdate() { 00467 wfDeprecated( __METHOD__, '1.18' ); 00468 } 00469 00475 public function isCurrent() { 00476 # If no oldid, this is the current version. 00477 if ( $this->getOldID() == 0 ) { 00478 return true; 00479 } 00480 00481 return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent(); 00482 } 00483 00491 public function getRevisionFetched() { 00492 $this->fetchContentObject(); 00493 00494 return $this->mRevision; 00495 } 00496 00502 public function getRevIdFetched() { 00503 if ( $this->mRevIdFetched ) { 00504 return $this->mRevIdFetched; 00505 } else { 00506 return $this->mPage->getLatest(); 00507 } 00508 } 00509 00514 public function view() { 00515 global $wgUseFileCache, $wgUseETag, $wgDebugToolbar; 00516 00517 wfProfileIn( __METHOD__ ); 00518 00519 # Get variables from query string 00520 # As side effect this will load the revision and update the title 00521 # in a revision ID is passed in the request, so this should remain 00522 # the first call of this method even if $oldid is used way below. 00523 $oldid = $this->getOldID(); 00524 00525 $user = $this->getContext()->getUser(); 00526 # Another whitelist check in case getOldID() is altering the title 00527 $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user ); 00528 if ( count( $permErrors ) ) { 00529 wfDebug( __METHOD__ . ": denied on secondary read check\n" ); 00530 wfProfileOut( __METHOD__ ); 00531 throw new PermissionsError( 'read', $permErrors ); 00532 } 00533 00534 $outputPage = $this->getContext()->getOutput(); 00535 # getOldID() may as well want us to redirect somewhere else 00536 if ( $this->mRedirectUrl ) { 00537 $outputPage->redirect( $this->mRedirectUrl ); 00538 wfDebug( __METHOD__ . ": redirecting due to oldid\n" ); 00539 wfProfileOut( __METHOD__ ); 00540 00541 return; 00542 } 00543 00544 # If we got diff in the query, we want to see a diff page instead of the article. 00545 if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) { 00546 wfDebug( __METHOD__ . ": showing diff page\n" ); 00547 $this->showDiffPage(); 00548 wfProfileOut( __METHOD__ ); 00549 00550 return; 00551 } 00552 00553 # Set page title (may be overridden by DISPLAYTITLE) 00554 $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() ); 00555 00556 $outputPage->setArticleFlag( true ); 00557 # Allow frames by default 00558 $outputPage->allowClickjacking(); 00559 00560 $parserCache = ParserCache::singleton(); 00561 00562 $parserOptions = $this->getParserOptions(); 00563 # Render printable version, use printable version cache 00564 if ( $outputPage->isPrintable() ) { 00565 $parserOptions->setIsPrintable( true ); 00566 $parserOptions->setEditSection( false ); 00567 } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) { 00568 $parserOptions->setEditSection( false ); 00569 } 00570 00571 # Try client and file cache 00572 if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) { 00573 if ( $wgUseETag ) { 00574 $outputPage->setETag( $parserCache->getETag( $this, $parserOptions ) ); 00575 } 00576 00577 # Is it client cached? 00578 if ( $outputPage->checkLastModified( $this->mPage->getTouched() ) ) { 00579 wfDebug( __METHOD__ . ": done 304\n" ); 00580 wfProfileOut( __METHOD__ ); 00581 00582 return; 00583 # Try file cache 00584 } elseif ( $wgUseFileCache && $this->tryFileCache() ) { 00585 wfDebug( __METHOD__ . ": done file cache\n" ); 00586 # tell wgOut that output is taken care of 00587 $outputPage->disable(); 00588 $this->mPage->doViewUpdates( $user ); 00589 wfProfileOut( __METHOD__ ); 00590 00591 return; 00592 } 00593 } 00594 00595 # Should the parser cache be used? 00596 $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid ); 00597 wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" ); 00598 if ( $user->getStubThreshold() ) { 00599 wfIncrStats( 'pcache_miss_stub' ); 00600 } 00601 00602 $this->showRedirectedFromHeader(); 00603 $this->showNamespaceHeader(); 00604 00605 # Iterate through the possible ways of constructing the output text. 00606 # Keep going until $outputDone is set, or we run out of things to do. 00607 $pass = 0; 00608 $outputDone = false; 00609 $this->mParserOutput = false; 00610 00611 while ( !$outputDone && ++$pass ) { 00612 switch( $pass ) { 00613 case 1: 00614 wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) ); 00615 break; 00616 case 2: 00617 # Early abort if the page doesn't exist 00618 if ( !$this->mPage->exists() ) { 00619 wfDebug( __METHOD__ . ": showing missing article\n" ); 00620 $this->showMissingArticle(); 00621 wfProfileOut( __METHOD__ ); 00622 return; 00623 } 00624 00625 # Try the parser cache 00626 if ( $useParserCache ) { 00627 $this->mParserOutput = $parserCache->get( $this, $parserOptions ); 00628 00629 if ( $this->mParserOutput !== false ) { 00630 if ( $oldid ) { 00631 wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" ); 00632 $this->setOldSubtitle( $oldid ); 00633 } else { 00634 wfDebug( __METHOD__ . ": showing parser cache contents\n" ); 00635 } 00636 $outputPage->addParserOutput( $this->mParserOutput ); 00637 # Ensure that UI elements requiring revision ID have 00638 # the correct version information. 00639 $outputPage->setRevisionId( $this->mPage->getLatest() ); 00640 # Preload timestamp to avoid a DB hit 00641 $cachedTimestamp = $this->mParserOutput->getTimestamp(); 00642 if ( $cachedTimestamp !== null ) { 00643 $outputPage->setRevisionTimestamp( $cachedTimestamp ); 00644 $this->mPage->setTimestamp( $cachedTimestamp ); 00645 } 00646 $outputDone = true; 00647 } 00648 } 00649 break; 00650 case 3: 00651 # This will set $this->mRevision if needed 00652 $this->fetchContentObject(); 00653 00654 # Are we looking at an old revision 00655 if ( $oldid && $this->mRevision ) { 00656 $this->setOldSubtitle( $oldid ); 00657 00658 if ( !$this->showDeletedRevisionHeader() ) { 00659 wfDebug( __METHOD__ . ": cannot view deleted revision\n" ); 00660 wfProfileOut( __METHOD__ ); 00661 return; 00662 } 00663 } 00664 00665 # Ensure that UI elements requiring revision ID have 00666 # the correct version information. 00667 $outputPage->setRevisionId( $this->getRevIdFetched() ); 00668 # Preload timestamp to avoid a DB hit 00669 $outputPage->setRevisionTimestamp( $this->getTimestamp() ); 00670 00671 # Pages containing custom CSS or JavaScript get special treatment 00672 if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) { 00673 wfDebug( __METHOD__ . ": showing CSS/JS source\n" ); 00674 $this->showCssOrJsPage(); 00675 $outputDone = true; 00676 } elseif( !wfRunHooks( 'ArticleContentViewCustom', 00677 array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { 00678 00679 # Allow extensions do their own custom view for certain pages 00680 $outputDone = true; 00681 } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', 00682 array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { 00683 00684 # Allow extensions do their own custom view for certain pages 00685 $outputDone = true; 00686 } else { 00687 $content = $this->getContentObject(); 00688 $rt = $content ? $content->getRedirectChain() : null; 00689 if ( $rt ) { 00690 wfDebug( __METHOD__ . ": showing redirect=no page\n" ); 00691 # Viewing a redirect page (e.g. with parameter redirect=no) 00692 $outputPage->addHTML( $this->viewRedirect( $rt ) ); 00693 # Parse just to get categories, displaytitle, etc. 00694 $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false ); 00695 $outputPage->addParserOutputNoText( $this->mParserOutput ); 00696 $outputDone = true; 00697 } 00698 } 00699 break; 00700 case 4: 00701 # Run the parse, protected by a pool counter 00702 wfDebug( __METHOD__ . ": doing uncached parse\n" ); 00703 00704 $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions, 00705 $this->getRevIdFetched(), $useParserCache, $this->getContentObject() ); 00706 00707 if ( !$poolArticleView->execute() ) { 00708 $error = $poolArticleView->getError(); 00709 if ( $error ) { 00710 $outputPage->clearHTML(); // for release() errors 00711 $outputPage->enableClientCache( false ); 00712 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 00713 00714 $errortext = $error->getWikiText( false, 'view-pool-error' ); 00715 $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' ); 00716 } 00717 # Connection or timeout error 00718 wfProfileOut( __METHOD__ ); 00719 return; 00720 } 00721 00722 $this->mParserOutput = $poolArticleView->getParserOutput(); 00723 $outputPage->addParserOutput( $this->mParserOutput ); 00724 00725 # Don't cache a dirty ParserOutput object 00726 if ( $poolArticleView->getIsDirty() ) { 00727 $outputPage->setSquidMaxage( 0 ); 00728 $outputPage->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" ); 00729 } 00730 00731 $outputDone = true; 00732 break; 00733 # Should be unreachable, but just in case... 00734 default: 00735 break 2; 00736 } 00737 } 00738 00739 # Get the ParserOutput actually *displayed* here. 00740 # Note that $this->mParserOutput is the *current* version output. 00741 $pOutput = ( $outputDone instanceof ParserOutput ) 00742 ? $outputDone // object fetched by hook 00743 : $this->mParserOutput; 00744 00745 # Adjust title for main page & pages with displaytitle 00746 if ( $pOutput ) { 00747 $this->adjustDisplayTitle( $pOutput ); 00748 } 00749 00750 # For the main page, overwrite the <title> element with the con- 00751 # tents of 'pagetitle-view-mainpage' instead of the default (if 00752 # that's not empty). 00753 # This message always exists because it is in the i18n files 00754 if ( $this->getTitle()->isMainPage() ) { 00755 $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage(); 00756 if ( !$msg->isDisabled() ) { 00757 $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() ); 00758 } 00759 } 00760 00761 # Check for any __NOINDEX__ tags on the page using $pOutput 00762 $policy = $this->getRobotPolicy( 'view', $pOutput ); 00763 $outputPage->setIndexPolicy( $policy['index'] ); 00764 $outputPage->setFollowPolicy( $policy['follow'] ); 00765 00766 $this->showViewFooter(); 00767 $this->mPage->doViewUpdates( $user ); 00768 00769 $outputPage->addModules( 'mediawiki.action.view.postEdit' ); 00770 00771 wfProfileOut( __METHOD__ ); 00772 } 00773 00778 public function adjustDisplayTitle( ParserOutput $pOutput ) { 00779 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion 00780 $titleText = $pOutput->getTitleText(); 00781 if ( strval( $titleText ) !== '' ) { 00782 $this->getContext()->getOutput()->setPageTitle( $titleText ); 00783 } 00784 } 00785 00792 public function showDiffPage() { 00793 $request = $this->getContext()->getRequest(); 00794 $user = $this->getContext()->getUser(); 00795 $diff = $request->getVal( 'diff' ); 00796 $rcid = $request->getVal( 'rcid' ); 00797 $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) ); 00798 $purge = $request->getVal( 'action' ) == 'purge'; 00799 $unhide = $request->getInt( 'unhide' ) == 1; 00800 $oldid = $this->getOldID(); 00801 00802 $rev = $this->getRevisionFetched(); 00803 00804 if ( !$rev ) { 00805 $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) ); 00806 $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 ); 00807 return; 00808 } 00809 00810 $contentHandler = $rev->getContentHandler(); 00811 $de = $contentHandler->createDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide ); 00812 00813 // DifferenceEngine directly fetched the revision: 00814 $this->mRevIdFetched = $de->mNewid; 00815 $de->showDiffPage( $diffOnly ); 00816 00817 if ( $diff == 0 || $diff == $this->mPage->getLatest() ) { 00818 # Run view updates for current revision only 00819 $this->mPage->doViewUpdates( $user ); 00820 } 00821 } 00822 00832 protected function showCssOrJsPage( $showCacheHint = true ) { 00833 $outputPage = $this->getContext()->getOutput(); 00834 00835 if ( $showCacheHint ) { 00836 $dir = $this->getContext()->getLanguage()->getDir(); 00837 $lang = $this->getContext()->getLanguage()->getCode(); 00838 00839 $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>", 00840 'clearyourcache' ); 00841 } 00842 00843 $this->fetchContentObject(); 00844 00845 if ( $this->mContentObject ) { 00846 // Give hooks a chance to customise the output 00847 if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mContentObject, $this->getTitle(), $outputPage ) ) ) { 00848 $po = $this->mContentObject->getParserOutput( $this->getTitle() ); 00849 $outputPage->addHTML( $po->getText() ); 00850 } 00851 } 00852 } 00853 00861 public function getRobotPolicy( $action, $pOutput ) { 00862 global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy; 00863 00864 $ns = $this->getTitle()->getNamespace(); 00865 00866 # Don't index user and user talk pages for blocked users (bug 11443) 00867 if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) { 00868 $specificTarget = null; 00869 $vagueTarget = null; 00870 $titleText = $this->getTitle()->getText(); 00871 if ( IP::isValid( $titleText ) ) { 00872 $vagueTarget = $titleText; 00873 } else { 00874 $specificTarget = $titleText; 00875 } 00876 if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) { 00877 return array( 00878 'index' => 'noindex', 00879 'follow' => 'nofollow' 00880 ); 00881 } 00882 } 00883 00884 if ( $this->mPage->getID() === 0 || $this->getOldID() ) { 00885 # Non-articles (special pages etc), and old revisions 00886 return array( 00887 'index' => 'noindex', 00888 'follow' => 'nofollow' 00889 ); 00890 } elseif ( $this->getContext()->getOutput()->isPrintable() ) { 00891 # Discourage indexing of printable versions, but encourage following 00892 return array( 00893 'index' => 'noindex', 00894 'follow' => 'follow' 00895 ); 00896 } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) { 00897 # For ?curid=x urls, disallow indexing 00898 return array( 00899 'index' => 'noindex', 00900 'follow' => 'follow' 00901 ); 00902 } 00903 00904 # Otherwise, construct the policy based on the various config variables. 00905 $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy ); 00906 00907 if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) { 00908 # Honour customised robot policies for this namespace 00909 $policy = array_merge( 00910 $policy, 00911 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] ) 00912 ); 00913 } 00914 if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) { 00915 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates 00916 # a final sanity check that we have really got the parser output. 00917 $policy = array_merge( 00918 $policy, 00919 array( 'index' => $pOutput->getIndexPolicy() ) 00920 ); 00921 } 00922 00923 if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) { 00924 # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__ 00925 $policy = array_merge( 00926 $policy, 00927 self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) 00928 ); 00929 } 00930 00931 return $policy; 00932 } 00933 00941 public static function formatRobotPolicy( $policy ) { 00942 if ( is_array( $policy ) ) { 00943 return $policy; 00944 } elseif ( !$policy ) { 00945 return array(); 00946 } 00947 00948 $policy = explode( ',', $policy ); 00949 $policy = array_map( 'trim', $policy ); 00950 00951 $arr = array(); 00952 foreach ( $policy as $var ) { 00953 if ( in_array( $var, array( 'index', 'noindex' ) ) ) { 00954 $arr['index'] = $var; 00955 } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) { 00956 $arr['follow'] = $var; 00957 } 00958 } 00959 00960 return $arr; 00961 } 00962 00970 public function showRedirectedFromHeader() { 00971 global $wgRedirectSources; 00972 $outputPage = $this->getContext()->getOutput(); 00973 00974 $rdfrom = $this->getContext()->getRequest()->getVal( 'rdfrom' ); 00975 00976 if ( isset( $this->mRedirectedFrom ) ) { 00977 // This is an internally redirected page view. 00978 // We'll need a backlink to the source page for navigation. 00979 if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) { 00980 $redir = Linker::linkKnown( 00981 $this->mRedirectedFrom, 00982 null, 00983 array(), 00984 array( 'redirect' => 'no' ) 00985 ); 00986 00987 $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) ); 00988 00989 // Set the fragment if one was specified in the redirect 00990 if ( strval( $this->getTitle()->getFragment() ) != '' ) { 00991 $fragment = Xml::escapeJsString( $this->getTitle()->getFragmentForURL() ); 00992 $outputPage->addInlineScript( "redirectToFragment(\"$fragment\");" ); 00993 } 00994 00995 // Add a <link rel="canonical"> tag 00996 $outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() ); 00997 00998 // Tell the output object that the user arrived at this article through a redirect 00999 $outputPage->setRedirectedFrom( $this->mRedirectedFrom ); 01000 01001 return true; 01002 } 01003 } elseif ( $rdfrom ) { 01004 // This is an externally redirected view, from some other wiki. 01005 // If it was reported from a trusted site, supply a backlink. 01006 if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { 01007 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom ); 01008 $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) ); 01009 01010 return true; 01011 } 01012 } 01013 01014 return false; 01015 } 01016 01021 public function showNamespaceHeader() { 01022 if ( $this->getTitle()->isTalkPage() ) { 01023 if ( !wfMessage( 'talkpageheader' )->isDisabled() ) { 01024 $this->getContext()->getOutput()->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1\n</div>", array( 'talkpageheader' ) ); 01025 } 01026 } 01027 } 01028 01032 public function showViewFooter() { 01033 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page 01034 if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) { 01035 $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' ); 01036 } 01037 01038 # If we have been passed an &rcid= parameter, we want to give the user a 01039 # chance to mark this new article as patrolled. 01040 $this->showPatrolFooter(); 01041 01042 wfRunHooks( 'ArticleViewFooter', array( $this ) ); 01043 01044 } 01045 01053 public function showPatrolFooter() { 01054 $request = $this->getContext()->getRequest(); 01055 $outputPage = $this->getContext()->getOutput(); 01056 $user = $this->getContext()->getUser(); 01057 $rcid = $request->getVal( 'rcid' ); 01058 01059 if ( !$rcid || !$this->getTitle()->quickUserCan( 'patrol', $user ) ) { 01060 return; 01061 } 01062 01063 $token = $user->getEditToken( $rcid ); 01064 01065 $outputPage->preventClickjacking(); 01066 $outputPage->addModules( 'mediawiki.page.patrol.ajax' ); 01067 01068 $link = Linker::linkKnown( 01069 $this->getTitle(), 01070 wfMessage( 'markaspatrolledtext' )->escaped(), 01071 array(), 01072 array( 01073 'action' => 'markpatrolled', 01074 'rcid' => $rcid, 01075 'token' => $token, 01076 ) 01077 ); 01078 01079 $outputPage->addHTML( 01080 "<div class='patrollink'>" . 01081 wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() . 01082 '</div>' 01083 ); 01084 } 01085 01090 public function showMissingArticle() { 01091 global $wgSend404Code; 01092 $outputPage = $this->getContext()->getOutput(); 01093 $validUserPage = false; 01094 01095 # Show info in user (talk) namespace. Does the user exist? Is he blocked? 01096 if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) { 01097 $parts = explode( '/', $this->getTitle()->getText() ); 01098 $rootPart = $parts[0]; 01099 $user = User::newFromName( $rootPart, false /* allow IP users*/ ); 01100 $ip = User::isIP( $rootPart ); 01101 01102 if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist 01103 $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>", 01104 array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) ); 01105 } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked 01106 LogEventsList::showLogExtract( 01107 $outputPage, 01108 'block', 01109 $user->getUserPage(), 01110 '', 01111 array( 01112 'lim' => 1, 01113 'showIfEmpty' => false, 01114 'msgKey' => array( 01115 'blocked-notice-logextract', 01116 $user->getName() # Support GENDER in notice 01117 ) 01118 ) 01119 ); 01120 $validUserPage = true; 01121 } else { 01122 $validUserPage = true; 01123 } 01124 } 01125 01126 wfRunHooks( 'ShowMissingArticle', array( $this ) ); 01127 01128 # Show delete and move logs 01129 LogEventsList::showLogExtract( $outputPage, array( 'delete', 'move' ), $this->getTitle(), '', 01130 array( 'lim' => 10, 01131 'conds' => array( "log_action != 'revision'" ), 01132 'showIfEmpty' => false, 01133 'msgKey' => array( 'moveddeleted-notice' ) ) 01134 ); 01135 01136 if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) { 01137 // If there's no backing content, send a 404 Not Found 01138 // for better machine handling of broken links. 01139 $this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" ); 01140 } 01141 01142 $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) ); 01143 01144 if ( ! $hookResult ) { 01145 return; 01146 } 01147 01148 # Show error message 01149 $oldid = $this->getOldID(); 01150 if ( $oldid ) { 01151 $text = wfMessage( 'missing-revision', $oldid )->plain(); 01152 } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) { 01153 // Use the default message text 01154 $text = $this->getTitle()->getDefaultMessageText(); 01155 } elseif ( $this->getTitle()->quickUserCan( 'create', $this->getContext()->getUser() ) 01156 && $this->getTitle()->quickUserCan( 'edit', $this->getContext()->getUser() ) 01157 ) { 01158 $text = wfMessage( 'noarticletext' )->plain(); 01159 } else { 01160 $text = wfMessage( 'noarticletext-nopermission' )->plain(); 01161 } 01162 $text = "<div class='noarticletext'>\n$text\n</div>"; 01163 01164 $outputPage->addWikiText( $text ); 01165 } 01166 01173 public function showDeletedRevisionHeader() { 01174 if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { 01175 // Not deleted 01176 return true; 01177 } 01178 01179 $outputPage = $this->getContext()->getOutput(); 01180 $user = $this->getContext()->getUser(); 01181 // If the user is not allowed to see it... 01182 if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) { 01183 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 01184 'rev-deleted-text-permission' ); 01185 01186 return false; 01187 // If the user needs to confirm that they want to see it... 01188 } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) { 01189 # Give explanation and add a link to view the revision... 01190 $oldid = intval( $this->getOldID() ); 01191 $link = $this->getTitle()->getFullUrl( "oldid={$oldid}&unhide=1" ); 01192 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ? 01193 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide'; 01194 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 01195 array( $msg, $link ) ); 01196 01197 return false; 01198 // We are allowed to see... 01199 } else { 01200 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ? 01201 'rev-suppressed-text-view' : 'rev-deleted-text-view'; 01202 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg ); 01203 01204 return true; 01205 } 01206 } 01207 01216 public function setOldSubtitle( $oldid = 0 ) { 01217 if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) { 01218 return; 01219 } 01220 01221 $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1; 01222 01223 # Cascade unhide param in links for easy deletion browsing 01224 $extraParams = array(); 01225 if ( $unhide ) { 01226 $extraParams['unhide'] = 1; 01227 } 01228 01229 if ( $this->mRevision && $this->mRevision->getId() === $oldid ) { 01230 $revision = $this->mRevision; 01231 } else { 01232 $revision = Revision::newFromId( $oldid ); 01233 } 01234 01235 $timestamp = $revision->getTimestamp(); 01236 01237 $current = ( $oldid == $this->mPage->getLatest() ); 01238 $language = $this->getContext()->getLanguage(); 01239 $user = $this->getContext()->getUser(); 01240 01241 $td = $language->userTimeAndDate( $timestamp, $user ); 01242 $tddate = $language->userDate( $timestamp, $user ); 01243 $tdtime = $language->userTime( $timestamp, $user ); 01244 01245 # Show user links if allowed to see them. If hidden, then show them only if requested... 01246 $userlinks = Linker::revUserTools( $revision, !$unhide ); 01247 01248 $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled() 01249 ? 'revision-info-current' 01250 : 'revision-info'; 01251 01252 $outputPage = $this->getContext()->getOutput(); 01253 $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg, 01254 $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate, 01255 $tdtime, $revision->getUser() )->parse() . "</div>" ); 01256 01257 $lnk = $current 01258 ? wfMessage( 'currentrevisionlink' )->escaped() 01259 : Linker::linkKnown( 01260 $this->getTitle(), 01261 wfMessage( 'currentrevisionlink' )->escaped(), 01262 array(), 01263 $extraParams 01264 ); 01265 $curdiff = $current 01266 ? wfMessage( 'diff' )->escaped() 01267 : Linker::linkKnown( 01268 $this->getTitle(), 01269 wfMessage( 'diff' )->escaped(), 01270 array(), 01271 array( 01272 'diff' => 'cur', 01273 'oldid' => $oldid 01274 ) + $extraParams 01275 ); 01276 $prev = $this->getTitle()->getPreviousRevisionID( $oldid ); 01277 $prevlink = $prev 01278 ? Linker::linkKnown( 01279 $this->getTitle(), 01280 wfMessage( 'previousrevision' )->escaped(), 01281 array(), 01282 array( 01283 'direction' => 'prev', 01284 'oldid' => $oldid 01285 ) + $extraParams 01286 ) 01287 : wfMessage( 'previousrevision' )->escaped(); 01288 $prevdiff = $prev 01289 ? Linker::linkKnown( 01290 $this->getTitle(), 01291 wfMessage( 'diff' )->escaped(), 01292 array(), 01293 array( 01294 'diff' => 'prev', 01295 'oldid' => $oldid 01296 ) + $extraParams 01297 ) 01298 : wfMessage( 'diff' )->escaped(); 01299 $nextlink = $current 01300 ? wfMessage( 'nextrevision' )->escaped() 01301 : Linker::linkKnown( 01302 $this->getTitle(), 01303 wfMessage( 'nextrevision' )->escaped(), 01304 array(), 01305 array( 01306 'direction' => 'next', 01307 'oldid' => $oldid 01308 ) + $extraParams 01309 ); 01310 $nextdiff = $current 01311 ? wfMessage( 'diff' )->escaped() 01312 : Linker::linkKnown( 01313 $this->getTitle(), 01314 wfMessage( 'diff' )->escaped(), 01315 array(), 01316 array( 01317 'diff' => 'next', 01318 'oldid' => $oldid 01319 ) + $extraParams 01320 ); 01321 01322 $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() ); 01323 if ( $cdel !== '' ) { 01324 $cdel .= ' '; 01325 } 01326 01327 $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel . 01328 wfMessage( 'revision-nav' )->rawParams( 01329 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff 01330 )->escaped() . "</div>" ); 01331 } 01332 01341 public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) { 01342 global $wgStylePath; 01343 01344 if ( !is_array( $target ) ) { 01345 $target = array( $target ); 01346 } 01347 01348 $lang = $this->getTitle()->getPageLanguage(); 01349 $imageDir = $lang->getDir(); 01350 01351 if ( $appendSubtitle ) { 01352 $out = $this->getContext()->getOutput(); 01353 $out->addSubtitle( wfMessage( 'redirectpagesub' )->escaped() ); 01354 } 01355 01356 // the loop prepends the arrow image before the link, so the first case needs to be outside 01357 01361 $title = array_shift( $target ); 01362 01363 if ( $forceKnown ) { 01364 $link = Linker::linkKnown( $title, htmlspecialchars( $title->getFullText() ) ); 01365 } else { 01366 $link = Linker::link( $title, htmlspecialchars( $title->getFullText() ) ); 01367 } 01368 01369 $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png'; 01370 $alt = $lang->isRTL() ? '←' : '→'; 01371 // Automatically append redirect=no to each link, since most of them are redirect pages themselves. 01372 foreach ( $target as $rt ) { 01373 $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) ); 01374 if ( $forceKnown ) { 01375 $link .= Linker::linkKnown( $rt, htmlspecialchars( $rt->getFullText(), array(), array( 'redirect' => 'no' ) ) ); 01376 } else { 01377 $link .= Linker::link( $rt, htmlspecialchars( $rt->getFullText() ), array(), array( 'redirect' => 'no' ) ); 01378 } 01379 } 01380 01381 $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png'; 01382 return '<div class="redirectMsg">' . 01383 Html::element( 'img', array( 'src' => $imageUrl, 'alt' => '#REDIRECT' ) ) . 01384 '<span class="redirectText">' . $link . '</span></div>'; 01385 } 01386 01390 public function render() { 01391 $this->getContext()->getOutput()->setArticleBodyOnly( true ); 01392 $this->view(); 01393 } 01394 01398 public function protect() { 01399 $form = new ProtectionForm( $this ); 01400 $form->execute(); 01401 } 01402 01406 public function unprotect() { 01407 $this->protect(); 01408 } 01409 01413 public function delete() { 01414 # This code desperately needs to be totally rewritten 01415 01416 $title = $this->getTitle(); 01417 $user = $this->getContext()->getUser(); 01418 01419 # Check permissions 01420 $permission_errors = $title->getUserPermissionsErrors( 'delete', $user ); 01421 if ( count( $permission_errors ) ) { 01422 throw new PermissionsError( 'delete', $permission_errors ); 01423 } 01424 01425 # Read-only check... 01426 if ( wfReadOnly() ) { 01427 throw new ReadOnlyError; 01428 } 01429 01430 # Better double-check that it hasn't been deleted yet! 01431 $this->mPage->loadPageData( 'fromdbmaster' ); 01432 if ( !$this->mPage->exists() ) { 01433 $deleteLogPage = new LogPage( 'delete' ); 01434 $outputPage = $this->getContext()->getOutput(); 01435 $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) ); 01436 $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>", 01437 array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ) 01438 ); 01439 $outputPage->addHTML( 01440 Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) 01441 ); 01442 LogEventsList::showLogExtract( 01443 $outputPage, 01444 'delete', 01445 $title 01446 ); 01447 01448 return; 01449 } 01450 01451 $request = $this->getContext()->getRequest(); 01452 $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' ); 01453 $deleteReason = $request->getText( 'wpReason' ); 01454 01455 if ( $deleteReasonList == 'other' ) { 01456 $reason = $deleteReason; 01457 } elseif ( $deleteReason != '' ) { 01458 // Entry from drop down menu + additional comment 01459 $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text(); 01460 $reason = $deleteReasonList . $colonseparator . $deleteReason; 01461 } else { 01462 $reason = $deleteReasonList; 01463 } 01464 01465 if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ), 01466 array( 'delete', $this->getTitle()->getPrefixedText() ) ) ) 01467 { 01468 # Flag to hide all contents of the archived revisions 01469 $suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' ); 01470 01471 $this->doDelete( $reason, $suppress ); 01472 01473 if ( $user->isLoggedIn() && $request->getCheck( 'wpWatch' ) != $user->isWatched( $title ) ) { 01474 if ( $request->getCheck( 'wpWatch' ) ) { 01475 WatchAction::doWatch( $title, $user ); 01476 } else { 01477 WatchAction::doUnwatch( $title, $user ); 01478 } 01479 } 01480 01481 return; 01482 } 01483 01484 // Generate deletion reason 01485 $hasHistory = false; 01486 if ( !$reason ) { 01487 try { 01488 $reason = $this->generateReason( $hasHistory ); 01489 } catch ( MWException $e ) { 01490 # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here. 01491 wfDebug( "Error while building auto delete summary: $e" ); 01492 $reason = ''; 01493 } 01494 } 01495 01496 // If the page has a history, insert a warning 01497 if ( $hasHistory ) { 01498 $revisions = $this->mTitle->estimateRevisionCount(); 01499 // @todo FIXME: i18n issue/patchwork message 01500 $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' . 01501 wfMessage( 'historywarning' )->numParams( $revisions )->parse() . 01502 wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title, 01503 wfMessage( 'history' )->escaped(), 01504 array( 'rel' => 'archives' ), 01505 array( 'action' => 'history' ) ) . 01506 '</strong>' 01507 ); 01508 01509 if ( $this->mTitle->isBigDeletion() ) { 01510 global $wgDeleteRevisionsLimit; 01511 $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", 01512 array( 'delete-warning-toobig', $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit ) ) ); 01513 } 01514 } 01515 01516 $this->confirmDelete( $reason ); 01517 } 01518 01524 public function confirmDelete( $reason ) { 01525 wfDebug( "Article::confirmDelete\n" ); 01526 01527 $outputPage = $this->getContext()->getOutput(); 01528 $outputPage->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) ); 01529 $outputPage->addBacklinkSubtitle( $this->getTitle() ); 01530 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 01531 $outputPage->addWikiMsg( 'confirmdeletetext' ); 01532 01533 wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) ); 01534 01535 $user = $this->getContext()->getUser(); 01536 01537 if ( $user->isAllowed( 'suppressrevision' ) ) { 01538 $suppress = "<tr id=\"wpDeleteSuppressRow\"> 01539 <td></td> 01540 <td class='mw-input'><strong>" . 01541 Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(), 01542 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) . 01543 "</strong></td> 01544 </tr>"; 01545 } else { 01546 $suppress = ''; 01547 } 01548 $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $this->getTitle() ); 01549 01550 $form = Xml::openElement( 'form', array( 'method' => 'post', 01551 'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) . 01552 Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) . 01553 Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) . 01554 Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) . 01555 "<tr id=\"wpDeleteReasonListRow\"> 01556 <td class='mw-label'>" . 01557 Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) . 01558 "</td> 01559 <td class='mw-input'>" . 01560 Xml::listDropDown( 'wpDeleteReasonList', 01561 wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(), 01562 wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(), '', 'wpReasonDropDown', 1 ) . 01563 "</td> 01564 </tr> 01565 <tr id=\"wpDeleteReasonRow\"> 01566 <td class='mw-label'>" . 01567 Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) . 01568 "</td> 01569 <td class='mw-input'>" . 01570 Html::input( 'wpReason', $reason, 'text', array( 01571 'size' => '60', 01572 'maxlength' => '255', 01573 'tabindex' => '2', 01574 'id' => 'wpReason', 01575 'autofocus' 01576 ) ) . 01577 "</td> 01578 </tr>"; 01579 01580 # Disallow watching if user is not logged in 01581 if ( $user->isLoggedIn() ) { 01582 $form .= " 01583 <tr> 01584 <td></td> 01585 <td class='mw-input'>" . 01586 Xml::checkLabel( wfMessage( 'watchthis' )->text(), 01587 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) . 01588 "</td> 01589 </tr>"; 01590 } 01591 01592 $form .= " 01593 $suppress 01594 <tr> 01595 <td></td> 01596 <td class='mw-submit'>" . 01597 Xml::submitButton( wfMessage( 'deletepage' )->text(), 01598 array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) . 01599 "</td> 01600 </tr>" . 01601 Xml::closeElement( 'table' ) . 01602 Xml::closeElement( 'fieldset' ) . 01603 Html::hidden( 'wpEditToken', $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) ) ) . 01604 Xml::closeElement( 'form' ); 01605 01606 if ( $user->isAllowed( 'editinterface' ) ) { 01607 $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' ); 01608 $link = Linker::link( 01609 $title, 01610 wfMessage( 'delete-edit-reasonlist' )->escaped(), 01611 array(), 01612 array( 'action' => 'edit' ) 01613 ); 01614 $form .= '<p class="mw-delete-editreasons">' . $link . '</p>'; 01615 } 01616 01617 $outputPage->addHTML( $form ); 01618 01619 $deleteLogPage = new LogPage( 'delete' ); 01620 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) ); 01621 LogEventsList::showLogExtract( $outputPage, 'delete', 01622 $this->getTitle() 01623 ); 01624 } 01625 01631 public function doDelete( $reason, $suppress = false ) { 01632 $error = ''; 01633 $outputPage = $this->getContext()->getOutput(); 01634 $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error ); 01635 if ( $status->isGood() ) { 01636 $deleted = $this->getTitle()->getPrefixedText(); 01637 01638 $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) ); 01639 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 01640 01641 $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]'; 01642 01643 $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink ); 01644 $outputPage->returnToMain( false ); 01645 } else { 01646 $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $this->getTitle()->getPrefixedText() ) ); 01647 if ( $error == '' ) { 01648 $outputPage->addWikiText( 01649 "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>" 01650 ); 01651 $deleteLogPage = new LogPage( 'delete' ); 01652 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) ); 01653 01654 LogEventsList::showLogExtract( 01655 $outputPage, 01656 'delete', 01657 $this->getTitle() 01658 ); 01659 } else { 01660 $outputPage->addHTML( $error ); 01661 } 01662 } 01663 } 01664 01665 /* Caching functions */ 01666 01674 protected function tryFileCache() { 01675 static $called = false; 01676 01677 if ( $called ) { 01678 wfDebug( "Article::tryFileCache(): called twice!?\n" ); 01679 return false; 01680 } 01681 01682 $called = true; 01683 if ( $this->isFileCacheable() ) { 01684 $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'view' ); 01685 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) { 01686 wfDebug( "Article::tryFileCache(): about to load file\n" ); 01687 $cache->loadFromFileCache( $this->getContext() ); 01688 return true; 01689 } else { 01690 wfDebug( "Article::tryFileCache(): starting buffer\n" ); 01691 ob_start( array( &$cache, 'saveToFileCache' ) ); 01692 } 01693 } else { 01694 wfDebug( "Article::tryFileCache(): not cacheable\n" ); 01695 } 01696 01697 return false; 01698 } 01699 01704 public function isFileCacheable() { 01705 $cacheable = false; 01706 01707 if ( HTMLFileCache::useFileCache( $this->getContext() ) ) { 01708 $cacheable = $this->mPage->getID() 01709 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect(); 01710 // Extension may have reason to disable file caching on some pages. 01711 if ( $cacheable ) { 01712 $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) ); 01713 } 01714 } 01715 01716 return $cacheable; 01717 } 01718 01732 public function getParserOutput( $oldid = null, User $user = null ) { 01733 //XXX: bypasses mParserOptions and thus setParserOptions() 01734 01735 if ( $user === null ) { 01736 $parserOptions = $this->getParserOptions(); 01737 } else { 01738 $parserOptions = $this->mPage->makeParserOptions( $user ); 01739 } 01740 01741 return $this->mPage->getParserOutput( $parserOptions, $oldid ); 01742 } 01743 01750 public function setParserOptions( ParserOptions $options ) { 01751 if ( $this->mParserOptions ) { 01752 throw new MWException( "can't change parser options after they have already been set" ); 01753 } 01754 01755 // clone, so if $options is modified later, it doesn't confuse the parser cache. 01756 $this->mParserOptions = clone $options; 01757 } 01758 01763 public function getParserOptions() { 01764 if ( !$this->mParserOptions ) { 01765 $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() ); 01766 } 01767 // Clone to allow modifications of the return value without affecting cache 01768 return clone $this->mParserOptions; 01769 } 01770 01777 public function setContext( $context ) { 01778 $this->mContext = $context; 01779 } 01780 01787 public function getContext() { 01788 if ( $this->mContext instanceof IContextSource ) { 01789 return $this->mContext; 01790 } else { 01791 wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" ); 01792 return RequestContext::getMain(); 01793 } 01794 } 01795 01800 public function info() { 01801 wfDeprecated( __METHOD__, '1.19' ); 01802 Action::factory( 'info', $this )->show(); 01803 } 01804 01809 public function markpatrolled() { 01810 wfDeprecated( __METHOD__, '1.18' ); 01811 Action::factory( 'markpatrolled', $this )->show(); 01812 } 01813 01819 public function purge() { 01820 return Action::factory( 'purge', $this )->show(); 01821 } 01822 01827 public function revert() { 01828 wfDeprecated( __METHOD__, '1.19' ); 01829 Action::factory( 'revert', $this )->show(); 01830 } 01831 01836 public function rollback() { 01837 wfDeprecated( __METHOD__, '1.19' ); 01838 Action::factory( 'rollback', $this )->show(); 01839 } 01840 01846 public function watch() { 01847 wfDeprecated( __METHOD__, '1.18' ); 01848 Action::factory( 'watch', $this )->show(); 01849 } 01850 01859 public function doWatch() { 01860 wfDeprecated( __METHOD__, '1.18' ); 01861 return WatchAction::doWatch( $this->getTitle(), $this->getContext()->getUser() ); 01862 } 01863 01869 public function unwatch() { 01870 wfDeprecated( __METHOD__, '1.18' ); 01871 Action::factory( 'unwatch', $this )->show(); 01872 } 01873 01879 public function doUnwatch() { 01880 wfDeprecated( __METHOD__, '1.18' ); 01881 return WatchAction::doUnwatch( $this->getTitle(), $this->getContext()->getUser() ); 01882 } 01883 01893 public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) { 01894 wfDeprecated( __METHOD__, '1.18' ); 01895 if ( $noRedir ) { 01896 $query = 'redirect=no'; 01897 if ( $extraQuery ) { 01898 $query .= "&$extraQuery"; 01899 } 01900 } else { 01901 $query = $extraQuery; 01902 } 01903 01904 $this->getContext()->getOutput()->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor ); 01905 } 01906 01913 public function __get( $fname ) { 01914 if ( property_exists( $this->mPage, $fname ) ) { 01915 #wfWarn( "Access to raw $fname field " . __CLASS__ ); 01916 return $this->mPage->$fname; 01917 } 01918 trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE ); 01919 } 01920 01928 public function __set( $fname, $fvalue ) { 01929 if ( property_exists( $this->mPage, $fname ) ) { 01930 #wfWarn( "Access to raw $fname field of " . __CLASS__ ); 01931 $this->mPage->$fname = $fvalue; 01932 // Note: extensions may want to toss on new fields 01933 } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) { 01934 $this->mPage->$fname = $fvalue; 01935 } else { 01936 trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE ); 01937 } 01938 } 01939 01948 public function __call( $fname, $args ) { 01949 if ( is_callable( array( $this->mPage, $fname ) ) ) { 01950 #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" ); 01951 return call_user_func_array( array( $this->mPage, $fname ), $args ); 01952 } 01953 trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR ); 01954 } 01955 01956 // ****** B/C functions to work-around PHP silliness with __call and references ****** // 01957 01966 public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) { 01967 return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user ); 01968 } 01969 01977 public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) { 01978 return $this->mPage->doUpdateRestrictions( 01979 $limit, 01980 $expiry, 01981 $cascade, 01982 $reason, 01983 $this->getContext()->getUser() 01984 ); 01985 } 01986 01995 public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) { 01996 return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error ); 01997 } 01998 02008 public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) { 02009 $user = is_null( $user ) ? $this->getContext()->getUser() : $user; 02010 return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user ); 02011 } 02012 02021 public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) { 02022 $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser; 02023 return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser ); 02024 } 02025 02030 public function generateReason( &$hasHistory ) { 02031 $title = $this->mPage->getTitle(); 02032 $handler = ContentHandler::getForTitle( $title ); 02033 return $handler->getAutoDeleteReason( $title, $hasHistory ); 02034 } 02035 02036 // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** // 02037 02041 public static function selectFields() { 02042 return WikiPage::selectFields(); 02043 } 02044 02048 public static function onArticleCreate( $title ) { 02049 WikiPage::onArticleCreate( $title ); 02050 } 02051 02055 public static function onArticleDelete( $title ) { 02056 WikiPage::onArticleDelete( $title ); 02057 } 02058 02062 public static function onArticleEdit( $title ) { 02063 WikiPage::onArticleEdit( $title ); 02064 } 02065 02073 public static function getAutosummary( $oldtext, $newtext, $flags ) { 02074 return WikiPage::getAutosummary( $oldtext, $newtext, $flags ); 02075 } 02076 // ****** 02077 }