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