MediaWiki
REL1_23
|
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, $context ) ); 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 00379 function fetchContent() { #BC cruft! 00380 ContentHandler::deprecated( __METHOD__, '1.21' ); 00381 00382 if ( $this->mContentLoaded && $this->mContent ) { 00383 return $this->mContent; 00384 } 00385 00386 wfProfileIn( __METHOD__ ); 00387 00388 $content = $this->fetchContentObject(); 00389 00390 // @todo Get rid of mContent everywhere! 00391 $this->mContent = ContentHandler::getContentText( $content ); 00392 ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); 00393 00394 wfProfileOut( __METHOD__ ); 00395 00396 return $this->mContent; 00397 } 00398 00411 protected function fetchContentObject() { 00412 if ( $this->mContentLoaded ) { 00413 return $this->mContentObject; 00414 } 00415 00416 wfProfileIn( __METHOD__ ); 00417 00418 $this->mContentLoaded = true; 00419 $this->mContent = null; 00420 00421 $oldid = $this->getOldID(); 00422 00423 # Pre-fill content with error message so that if something 00424 # fails we'll have something telling us what we intended. 00425 //XXX: this isn't page content but a UI message. horrible. 00426 $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() ); 00427 00428 if ( $oldid ) { 00429 # $this->mRevision might already be fetched by getOldIDFromRequest() 00430 if ( !$this->mRevision ) { 00431 $this->mRevision = Revision::newFromId( $oldid ); 00432 if ( !$this->mRevision ) { 00433 wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" ); 00434 wfProfileOut( __METHOD__ ); 00435 return false; 00436 } 00437 } 00438 } else { 00439 if ( !$this->mPage->getLatest() ) { 00440 wfDebug( __METHOD__ . " failed to find page data for title " . 00441 $this->getTitle()->getPrefixedText() . "\n" ); 00442 wfProfileOut( __METHOD__ ); 00443 return false; 00444 } 00445 00446 $this->mRevision = $this->mPage->getRevision(); 00447 00448 if ( !$this->mRevision ) { 00449 wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . 00450 $this->mPage->getLatest() . "\n" ); 00451 wfProfileOut( __METHOD__ ); 00452 return false; 00453 } 00454 } 00455 00456 // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks. 00457 // We should instead work with the Revision object when we need it... 00458 // Loads if user is allowed 00459 $this->mContentObject = $this->mRevision->getContent( 00460 Revision::FOR_THIS_USER, 00461 $this->getContext()->getUser() 00462 ); 00463 $this->mRevIdFetched = $this->mRevision->getId(); 00464 00465 wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); 00466 00467 wfProfileOut( __METHOD__ ); 00468 00469 return $this->mContentObject; 00470 } 00471 00477 public function isCurrent() { 00478 # If no oldid, this is the current version. 00479 if ( $this->getOldID() == 0 ) { 00480 return true; 00481 } 00482 00483 return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent(); 00484 } 00485 00493 public function getRevisionFetched() { 00494 $this->fetchContentObject(); 00495 00496 return $this->mRevision; 00497 } 00498 00504 public function getRevIdFetched() { 00505 if ( $this->mRevIdFetched ) { 00506 return $this->mRevIdFetched; 00507 } else { 00508 return $this->mPage->getLatest(); 00509 } 00510 } 00511 00516 public function view() { 00517 global $wgUseFileCache, $wgUseETag, $wgDebugToolbar; 00518 00519 wfProfileIn( __METHOD__ ); 00520 00521 # Get variables from query string 00522 # As side effect this will load the revision and update the title 00523 # in a revision ID is passed in the request, so this should remain 00524 # the first call of this method even if $oldid is used way below. 00525 $oldid = $this->getOldID(); 00526 00527 $user = $this->getContext()->getUser(); 00528 # Another whitelist check in case getOldID() is altering the title 00529 $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user ); 00530 if ( count( $permErrors ) ) { 00531 wfDebug( __METHOD__ . ": denied on secondary read check\n" ); 00532 wfProfileOut( __METHOD__ ); 00533 throw new PermissionsError( 'read', $permErrors ); 00534 } 00535 00536 $outputPage = $this->getContext()->getOutput(); 00537 # getOldID() may as well want us to redirect somewhere else 00538 if ( $this->mRedirectUrl ) { 00539 $outputPage->redirect( $this->mRedirectUrl ); 00540 wfDebug( __METHOD__ . ": redirecting due to oldid\n" ); 00541 wfProfileOut( __METHOD__ ); 00542 00543 return; 00544 } 00545 00546 # If we got diff in the query, we want to see a diff page instead of the article. 00547 if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) { 00548 wfDebug( __METHOD__ . ": showing diff page\n" ); 00549 $this->showDiffPage(); 00550 wfProfileOut( __METHOD__ ); 00551 00552 return; 00553 } 00554 00555 # Set page title (may be overridden by DISPLAYTITLE) 00556 $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() ); 00557 00558 $outputPage->setArticleFlag( true ); 00559 # Allow frames by default 00560 $outputPage->allowClickjacking(); 00561 00562 $parserCache = ParserCache::singleton(); 00563 00564 $parserOptions = $this->getParserOptions(); 00565 # Render printable version, use printable version cache 00566 if ( $outputPage->isPrintable() ) { 00567 $parserOptions->setIsPrintable( true ); 00568 $parserOptions->setEditSection( false ); 00569 } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) { 00570 $parserOptions->setEditSection( false ); 00571 } 00572 00573 # Try client and file cache 00574 if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) { 00575 if ( $wgUseETag ) { 00576 $outputPage->setETag( $parserCache->getETag( $this, $parserOptions ) ); 00577 } 00578 00579 # Is it client cached? 00580 if ( $outputPage->checkLastModified( $this->mPage->getTouched() ) ) { 00581 wfDebug( __METHOD__ . ": done 304\n" ); 00582 wfProfileOut( __METHOD__ ); 00583 00584 return; 00585 # Try file cache 00586 } elseif ( $wgUseFileCache && $this->tryFileCache() ) { 00587 wfDebug( __METHOD__ . ": done file cache\n" ); 00588 # tell wgOut that output is taken care of 00589 $outputPage->disable(); 00590 $this->mPage->doViewUpdates( $user, $oldid ); 00591 wfProfileOut( __METHOD__ ); 00592 00593 return; 00594 } 00595 } 00596 00597 # Should the parser cache be used? 00598 $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid ); 00599 wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" ); 00600 if ( $user->getStubThreshold() ) { 00601 wfIncrStats( 'pcache_miss_stub' ); 00602 } 00603 00604 $this->showRedirectedFromHeader(); 00605 $this->showNamespaceHeader(); 00606 00607 # Iterate through the possible ways of constructing the output text. 00608 # Keep going until $outputDone is set, or we run out of things to do. 00609 $pass = 0; 00610 $outputDone = false; 00611 $this->mParserOutput = false; 00612 00613 while ( !$outputDone && ++$pass ) { 00614 switch ( $pass ) { 00615 case 1: 00616 wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) ); 00617 break; 00618 case 2: 00619 # Early abort if the page doesn't exist 00620 if ( !$this->mPage->exists() ) { 00621 wfDebug( __METHOD__ . ": showing missing article\n" ); 00622 $this->showMissingArticle(); 00623 $this->mPage->doViewUpdates( $user ); 00624 wfProfileOut( __METHOD__ ); 00625 return; 00626 } 00627 00628 # Try the parser cache 00629 if ( $useParserCache ) { 00630 $this->mParserOutput = $parserCache->get( $this, $parserOptions ); 00631 00632 if ( $this->mParserOutput !== false ) { 00633 if ( $oldid ) { 00634 wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" ); 00635 $this->setOldSubtitle( $oldid ); 00636 } else { 00637 wfDebug( __METHOD__ . ": showing parser cache contents\n" ); 00638 } 00639 $outputPage->addParserOutput( $this->mParserOutput ); 00640 # Ensure that UI elements requiring revision ID have 00641 # the correct version information. 00642 $outputPage->setRevisionId( $this->mPage->getLatest() ); 00643 # Preload timestamp to avoid a DB hit 00644 $cachedTimestamp = $this->mParserOutput->getTimestamp(); 00645 if ( $cachedTimestamp !== null ) { 00646 $outputPage->setRevisionTimestamp( $cachedTimestamp ); 00647 $this->mPage->setTimestamp( $cachedTimestamp ); 00648 } 00649 $outputDone = true; 00650 } 00651 } 00652 break; 00653 case 3: 00654 # This will set $this->mRevision if needed 00655 $this->fetchContentObject(); 00656 00657 # Are we looking at an old revision 00658 if ( $oldid && $this->mRevision ) { 00659 $this->setOldSubtitle( $oldid ); 00660 00661 if ( !$this->showDeletedRevisionHeader() ) { 00662 wfDebug( __METHOD__ . ": cannot view deleted revision\n" ); 00663 wfProfileOut( __METHOD__ ); 00664 return; 00665 } 00666 } 00667 00668 # Ensure that UI elements requiring revision ID have 00669 # the correct version information. 00670 $outputPage->setRevisionId( $this->getRevIdFetched() ); 00671 # Preload timestamp to avoid a DB hit 00672 $outputPage->setRevisionTimestamp( $this->getTimestamp() ); 00673 00674 # Pages containing custom CSS or JavaScript get special treatment 00675 if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) { 00676 wfDebug( __METHOD__ . ": showing CSS/JS source\n" ); 00677 $this->showCssOrJsPage(); 00678 $outputDone = true; 00679 } elseif ( !wfRunHooks( 'ArticleContentViewCustom', 00680 array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { 00681 00682 # Allow extensions do their own custom view for certain pages 00683 $outputDone = true; 00684 } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', 00685 array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { 00686 00687 # Allow extensions do their own custom view for certain pages 00688 $outputDone = true; 00689 } else { 00690 $content = $this->getContentObject(); 00691 $rt = $content ? $content->getRedirectChain() : null; 00692 if ( $rt ) { 00693 wfDebug( __METHOD__ . ": showing redirect=no page\n" ); 00694 # Viewing a redirect page (e.g. with parameter redirect=no) 00695 $outputPage->addHTML( $this->viewRedirect( $rt ) ); 00696 # Parse just to get categories, displaytitle, etc. 00697 $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false ); 00698 $outputPage->addParserOutputNoText( $this->mParserOutput ); 00699 $outputDone = true; 00700 } 00701 } 00702 break; 00703 case 4: 00704 # Run the parse, protected by a pool counter 00705 wfDebug( __METHOD__ . ": doing uncached parse\n" ); 00706 00707 $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions, 00708 $this->getRevIdFetched(), $useParserCache, $this->getContentObject() ); 00709 00710 if ( !$poolArticleView->execute() ) { 00711 $error = $poolArticleView->getError(); 00712 if ( $error ) { 00713 $outputPage->clearHTML(); // for release() errors 00714 $outputPage->enableClientCache( false ); 00715 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 00716 00717 $errortext = $error->getWikiText( false, 'view-pool-error' ); 00718 $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' ); 00719 } 00720 # Connection or timeout error 00721 wfProfileOut( __METHOD__ ); 00722 return; 00723 } 00724 00725 $this->mParserOutput = $poolArticleView->getParserOutput(); 00726 $outputPage->addParserOutput( $this->mParserOutput ); 00727 00728 # Don't cache a dirty ParserOutput object 00729 if ( $poolArticleView->getIsDirty() ) { 00730 $outputPage->setSquidMaxage( 0 ); 00731 $outputPage->addHTML( "<!-- parser cache is expired, " . 00732 "sending anyway due to pool overload-->\n" ); 00733 } 00734 00735 $outputDone = true; 00736 break; 00737 # Should be unreachable, but just in case... 00738 default: 00739 break 2; 00740 } 00741 } 00742 00743 # Get the ParserOutput actually *displayed* here. 00744 # Note that $this->mParserOutput is the *current* version output. 00745 $pOutput = ( $outputDone instanceof ParserOutput ) 00746 ? $outputDone // object fetched by hook 00747 : $this->mParserOutput; 00748 00749 # Adjust title for main page & pages with displaytitle 00750 if ( $pOutput ) { 00751 $this->adjustDisplayTitle( $pOutput ); 00752 } 00753 00754 # For the main page, overwrite the <title> element with the con- 00755 # tents of 'pagetitle-view-mainpage' instead of the default (if 00756 # that's not empty). 00757 # This message always exists because it is in the i18n files 00758 if ( $this->getTitle()->isMainPage() ) { 00759 $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage(); 00760 if ( !$msg->isDisabled() ) { 00761 $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() ); 00762 } 00763 } 00764 00765 # Check for any __NOINDEX__ tags on the page using $pOutput 00766 $policy = $this->getRobotPolicy( 'view', $pOutput ); 00767 $outputPage->setIndexPolicy( $policy['index'] ); 00768 $outputPage->setFollowPolicy( $policy['follow'] ); 00769 00770 $this->showViewFooter(); 00771 $this->mPage->doViewUpdates( $user, $oldid ); 00772 00773 $outputPage->addModules( 'mediawiki.action.view.postEdit' ); 00774 00775 wfProfileOut( __METHOD__ ); 00776 } 00777 00782 public function adjustDisplayTitle( ParserOutput $pOutput ) { 00783 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion 00784 $titleText = $pOutput->getTitleText(); 00785 if ( strval( $titleText ) !== '' ) { 00786 $this->getContext()->getOutput()->setPageTitle( $titleText ); 00787 } 00788 } 00789 00796 public function showDiffPage() { 00797 $request = $this->getContext()->getRequest(); 00798 $user = $this->getContext()->getUser(); 00799 $diff = $request->getVal( 'diff' ); 00800 $rcid = $request->getVal( 'rcid' ); 00801 $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) ); 00802 $purge = $request->getVal( 'action' ) == 'purge'; 00803 $unhide = $request->getInt( 'unhide' ) == 1; 00804 $oldid = $this->getOldID(); 00805 00806 $rev = $this->getRevisionFetched(); 00807 00808 if ( !$rev ) { 00809 $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) ); 00810 $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 ); 00811 return; 00812 } 00813 00814 $contentHandler = $rev->getContentHandler(); 00815 $de = $contentHandler->createDifferenceEngine( 00816 $this->getContext(), 00817 $oldid, 00818 $diff, 00819 $rcid, 00820 $purge, 00821 $unhide 00822 ); 00823 00824 // DifferenceEngine directly fetched the revision: 00825 $this->mRevIdFetched = $de->mNewid; 00826 $de->showDiffPage( $diffOnly ); 00827 00828 // Run view updates for the newer revision being diffed (and shown 00829 // below the diff if not $diffOnly). 00830 list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff ); 00831 // New can be false, convert it to 0 - this conveniently means the latest revision 00832 $this->mPage->doViewUpdates( $user, (int)$new ); 00833 } 00834 00845 protected function showCssOrJsPage( $showCacheHint = true ) { 00846 $outputPage = $this->getContext()->getOutput(); 00847 00848 if ( $showCacheHint ) { 00849 $dir = $this->getContext()->getLanguage()->getDir(); 00850 $lang = $this->getContext()->getLanguage()->getCode(); 00851 00852 $outputPage->wrapWikiMsg( 00853 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>", 00854 'clearyourcache' 00855 ); 00856 } 00857 00858 $this->fetchContentObject(); 00859 00860 if ( $this->mContentObject ) { 00861 // Give hooks a chance to customise the output 00862 if ( ContentHandler::runLegacyHooks( 00863 'ShowRawCssJs', 00864 array( $this->mContentObject, $this->getTitle(), $outputPage ) ) 00865 ) { 00866 $po = $this->mContentObject->getParserOutput( $this->getTitle() ); 00867 $outputPage->addHTML( $po->getText() ); 00868 } 00869 } 00870 } 00871 00879 public function getRobotPolicy( $action, $pOutput = null ) { 00880 global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy; 00881 00882 $ns = $this->getTitle()->getNamespace(); 00883 00884 # Don't index user and user talk pages for blocked users (bug 11443) 00885 if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) { 00886 $specificTarget = null; 00887 $vagueTarget = null; 00888 $titleText = $this->getTitle()->getText(); 00889 if ( IP::isValid( $titleText ) ) { 00890 $vagueTarget = $titleText; 00891 } else { 00892 $specificTarget = $titleText; 00893 } 00894 if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) { 00895 return array( 00896 'index' => 'noindex', 00897 'follow' => 'nofollow' 00898 ); 00899 } 00900 } 00901 00902 if ( $this->mPage->getID() === 0 || $this->getOldID() ) { 00903 # Non-articles (special pages etc), and old revisions 00904 return array( 00905 'index' => 'noindex', 00906 'follow' => 'nofollow' 00907 ); 00908 } elseif ( $this->getContext()->getOutput()->isPrintable() ) { 00909 # Discourage indexing of printable versions, but encourage following 00910 return array( 00911 'index' => 'noindex', 00912 'follow' => 'follow' 00913 ); 00914 } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) { 00915 # For ?curid=x urls, disallow indexing 00916 return array( 00917 'index' => 'noindex', 00918 'follow' => 'follow' 00919 ); 00920 } 00921 00922 # Otherwise, construct the policy based on the various config variables. 00923 $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy ); 00924 00925 if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) { 00926 # Honour customised robot policies for this namespace 00927 $policy = array_merge( 00928 $policy, 00929 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] ) 00930 ); 00931 } 00932 if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) { 00933 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates 00934 # a final sanity check that we have really got the parser output. 00935 $policy = array_merge( 00936 $policy, 00937 array( 'index' => $pOutput->getIndexPolicy() ) 00938 ); 00939 } 00940 00941 if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) { 00942 # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__ 00943 $policy = array_merge( 00944 $policy, 00945 self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) 00946 ); 00947 } 00948 00949 return $policy; 00950 } 00951 00959 public static function formatRobotPolicy( $policy ) { 00960 if ( is_array( $policy ) ) { 00961 return $policy; 00962 } elseif ( !$policy ) { 00963 return array(); 00964 } 00965 00966 $policy = explode( ',', $policy ); 00967 $policy = array_map( 'trim', $policy ); 00968 00969 $arr = array(); 00970 foreach ( $policy as $var ) { 00971 if ( in_array( $var, array( 'index', 'noindex' ) ) ) { 00972 $arr['index'] = $var; 00973 } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) { 00974 $arr['follow'] = $var; 00975 } 00976 } 00977 00978 return $arr; 00979 } 00980 00988 public function showRedirectedFromHeader() { 00989 global $wgRedirectSources; 00990 $outputPage = $this->getContext()->getOutput(); 00991 00992 $rdfrom = $this->getContext()->getRequest()->getVal( 'rdfrom' ); 00993 00994 if ( isset( $this->mRedirectedFrom ) ) { 00995 // This is an internally redirected page view. 00996 // We'll need a backlink to the source page for navigation. 00997 if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) { 00998 $redir = Linker::linkKnown( 00999 $this->mRedirectedFrom, 01000 null, 01001 array(), 01002 array( 'redirect' => 'no' ) 01003 ); 01004 01005 $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) ); 01006 01007 // Set the fragment if one was specified in the redirect 01008 if ( $this->getTitle()->hasFragment() ) { 01009 $outputPage->addJsConfigVars( 'wgRedirectToFragment', $this->getTitle()->getFragmentForURL() ); 01010 $outputPage->addModules( 'mediawiki.action.view.redirectToFragment' ); 01011 } 01012 01013 // Add a <link rel="canonical"> tag 01014 $outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() ); 01015 01016 // Tell the output object that the user arrived at this article through a redirect 01017 $outputPage->setRedirectedFrom( $this->mRedirectedFrom ); 01018 01019 return true; 01020 } 01021 } elseif ( $rdfrom ) { 01022 // This is an externally redirected view, from some other wiki. 01023 // If it was reported from a trusted site, supply a backlink. 01024 if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { 01025 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom ); 01026 $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) ); 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 $outputPage = $this->getContext()->getOutput(); 01194 // Whether the page is a root user page of an existing user (but not a subpage) 01195 $validUserPage = false; 01196 01197 # Show info in user (talk) namespace. Does the user exist? Is he blocked? 01198 if ( $this->getTitle()->getNamespace() == NS_USER 01199 || $this->getTitle()->getNamespace() == NS_USER_TALK 01200 ) { 01201 $parts = explode( '/', $this->getTitle()->getText() ); 01202 $rootPart = $parts[0]; 01203 $user = User::newFromName( $rootPart, false /* allow IP users*/ ); 01204 $ip = User::isIP( $rootPart ); 01205 01206 if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist 01207 $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>", 01208 array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) ); 01209 } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked 01210 LogEventsList::showLogExtract( 01211 $outputPage, 01212 'block', 01213 $user->getUserPage(), 01214 '', 01215 array( 01216 'lim' => 1, 01217 'showIfEmpty' => false, 01218 'msgKey' => array( 01219 'blocked-notice-logextract', 01220 $user->getName() # Support GENDER in notice 01221 ) 01222 ) 01223 ); 01224 $validUserPage = !$this->getTitle()->isSubpage(); 01225 } else { 01226 $validUserPage = !$this->getTitle()->isSubpage(); 01227 } 01228 } 01229 01230 wfRunHooks( 'ShowMissingArticle', array( $this ) ); 01231 01232 // Give extensions a chance to hide their (unrelated) log entries 01233 $logTypes = array( 'delete', 'move' ); 01234 $conds = array( "log_action != 'revision'" ); 01235 wfRunHooks( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) ); 01236 01237 # Show delete and move logs 01238 LogEventsList::showLogExtract( $outputPage, $logTypes, $this->getTitle(), '', 01239 array( 'lim' => 10, 01240 'conds' => $conds, 01241 'showIfEmpty' => false, 01242 'msgKey' => array( 'moveddeleted-notice' ) ) 01243 ); 01244 01245 if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) { 01246 // If there's no backing content, send a 404 Not Found 01247 // for better machine handling of broken links. 01248 $this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" ); 01249 } 01250 01251 if ( $validUserPage ) { 01252 // Also apply the robot policy for nonexisting user pages (as those aren't served as 404) 01253 $policy = $this->getRobotPolicy( 'view' ); 01254 $outputPage->setIndexPolicy( $policy['index'] ); 01255 $outputPage->setFollowPolicy( $policy['follow'] ); 01256 } 01257 01258 $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) ); 01259 01260 if ( ! $hookResult ) { 01261 return; 01262 } 01263 01264 # Show error message 01265 $oldid = $this->getOldID(); 01266 if ( $oldid ) { 01267 $text = wfMessage( 'missing-revision', $oldid )->plain(); 01268 } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) { 01269 // Use the default message text 01270 $text = $this->getTitle()->getDefaultMessageText(); 01271 } elseif ( $this->getTitle()->quickUserCan( 'create', $this->getContext()->getUser() ) 01272 && $this->getTitle()->quickUserCan( 'edit', $this->getContext()->getUser() ) 01273 ) { 01274 $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon'; 01275 $text = wfMessage( $message )->plain(); 01276 } else { 01277 $text = wfMessage( 'noarticletext-nopermission' )->plain(); 01278 } 01279 $text = "<div class='noarticletext'>\n$text\n</div>"; 01280 01281 $outputPage->addWikiText( $text ); 01282 } 01283 01290 public function showDeletedRevisionHeader() { 01291 if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { 01292 // Not deleted 01293 return true; 01294 } 01295 01296 $outputPage = $this->getContext()->getOutput(); 01297 $user = $this->getContext()->getUser(); 01298 // If the user is not allowed to see it... 01299 if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) { 01300 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 01301 'rev-deleted-text-permission' ); 01302 01303 return false; 01304 // If the user needs to confirm that they want to see it... 01305 } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) { 01306 # Give explanation and add a link to view the revision... 01307 $oldid = intval( $this->getOldID() ); 01308 $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" ); 01309 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ? 01310 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide'; 01311 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 01312 array( $msg, $link ) ); 01313 01314 return false; 01315 // We are allowed to see... 01316 } else { 01317 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ? 01318 'rev-suppressed-text-view' : 'rev-deleted-text-view'; 01319 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg ); 01320 01321 return true; 01322 } 01323 } 01324 01333 public function setOldSubtitle( $oldid = 0 ) { 01334 if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) { 01335 return; 01336 } 01337 01338 $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1; 01339 01340 # Cascade unhide param in links for easy deletion browsing 01341 $extraParams = array(); 01342 if ( $unhide ) { 01343 $extraParams['unhide'] = 1; 01344 } 01345 01346 if ( $this->mRevision && $this->mRevision->getId() === $oldid ) { 01347 $revision = $this->mRevision; 01348 } else { 01349 $revision = Revision::newFromId( $oldid ); 01350 } 01351 01352 $timestamp = $revision->getTimestamp(); 01353 01354 $current = ( $oldid == $this->mPage->getLatest() ); 01355 $language = $this->getContext()->getLanguage(); 01356 $user = $this->getContext()->getUser(); 01357 01358 $td = $language->userTimeAndDate( $timestamp, $user ); 01359 $tddate = $language->userDate( $timestamp, $user ); 01360 $tdtime = $language->userTime( $timestamp, $user ); 01361 01362 # Show user links if allowed to see them. If hidden, then show them only if requested... 01363 $userlinks = Linker::revUserTools( $revision, !$unhide ); 01364 01365 $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled() 01366 ? 'revision-info-current' 01367 : 'revision-info'; 01368 01369 $outputPage = $this->getContext()->getOutput(); 01370 $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg, 01371 $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate, 01372 $tdtime, $revision->getUser() )->parse() . "</div>" ); 01373 01374 $lnk = $current 01375 ? wfMessage( 'currentrevisionlink' )->escaped() 01376 : Linker::linkKnown( 01377 $this->getTitle(), 01378 wfMessage( 'currentrevisionlink' )->escaped(), 01379 array(), 01380 $extraParams 01381 ); 01382 $curdiff = $current 01383 ? wfMessage( 'diff' )->escaped() 01384 : Linker::linkKnown( 01385 $this->getTitle(), 01386 wfMessage( 'diff' )->escaped(), 01387 array(), 01388 array( 01389 'diff' => 'cur', 01390 'oldid' => $oldid 01391 ) + $extraParams 01392 ); 01393 $prev = $this->getTitle()->getPreviousRevisionID( $oldid ); 01394 $prevlink = $prev 01395 ? Linker::linkKnown( 01396 $this->getTitle(), 01397 wfMessage( 'previousrevision' )->escaped(), 01398 array(), 01399 array( 01400 'direction' => 'prev', 01401 'oldid' => $oldid 01402 ) + $extraParams 01403 ) 01404 : wfMessage( 'previousrevision' )->escaped(); 01405 $prevdiff = $prev 01406 ? Linker::linkKnown( 01407 $this->getTitle(), 01408 wfMessage( 'diff' )->escaped(), 01409 array(), 01410 array( 01411 'diff' => 'prev', 01412 'oldid' => $oldid 01413 ) + $extraParams 01414 ) 01415 : wfMessage( 'diff' )->escaped(); 01416 $nextlink = $current 01417 ? wfMessage( 'nextrevision' )->escaped() 01418 : Linker::linkKnown( 01419 $this->getTitle(), 01420 wfMessage( 'nextrevision' )->escaped(), 01421 array(), 01422 array( 01423 'direction' => 'next', 01424 'oldid' => $oldid 01425 ) + $extraParams 01426 ); 01427 $nextdiff = $current 01428 ? wfMessage( 'diff' )->escaped() 01429 : Linker::linkKnown( 01430 $this->getTitle(), 01431 wfMessage( 'diff' )->escaped(), 01432 array(), 01433 array( 01434 'diff' => 'next', 01435 'oldid' => $oldid 01436 ) + $extraParams 01437 ); 01438 01439 $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() ); 01440 if ( $cdel !== '' ) { 01441 $cdel .= ' '; 01442 } 01443 01444 $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel . 01445 wfMessage( 'revision-nav' )->rawParams( 01446 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff 01447 )->escaped() . "</div>" ); 01448 } 01449 01458 public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) { 01459 global $wgStylePath; 01460 01461 if ( !is_array( $target ) ) { 01462 $target = array( $target ); 01463 } 01464 01465 $lang = $this->getTitle()->getPageLanguage(); 01466 $imageDir = $lang->getDir(); 01467 01468 if ( $appendSubtitle ) { 01469 $out = $this->getContext()->getOutput(); 01470 $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() ); 01471 } 01472 01473 // the loop prepends the arrow image before the link, so the first case needs to be outside 01474 01476 $title = array_shift( $target ); 01477 01478 if ( $forceKnown ) { 01479 $link = Linker::linkKnown( $title, htmlspecialchars( $title->getFullText() ) ); 01480 } else { 01481 $link = Linker::link( $title, htmlspecialchars( $title->getFullText() ) ); 01482 } 01483 01484 $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png'; 01485 $alt = $lang->isRTL() ? '←' : '→'; 01486 01487 // Automatically append redirect=no to each link, since most of them are 01488 // redirect pages themselves. 01490 foreach ( $target as $rt ) { 01491 $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) ); 01492 if ( $forceKnown ) { 01493 $link .= Linker::linkKnown( 01494 $rt, 01495 htmlspecialchars( $rt->getFullText(), 01496 array(), 01497 array( 'redirect' => 'no' ) 01498 ) 01499 ); 01500 } else { 01501 $link .= Linker::link( 01502 $rt, 01503 htmlspecialchars( $rt->getFullText() ), 01504 array(), 01505 array( 'redirect' => 'no' ) 01506 ); 01507 } 01508 } 01509 01510 $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png'; 01511 return '<div class="redirectMsg">' . 01512 Html::element( 'img', array( 'src' => $imageUrl, 'alt' => '#REDIRECT' ) ) . 01513 '<span class="redirectText">' . $link . '</span></div>'; 01514 } 01515 01519 public function render() { 01520 $this->getContext()->getOutput()->setArticleBodyOnly( true ); 01521 $this->getContext()->getOutput()->enableSectionEditLinks( false ); 01522 $this->view(); 01523 } 01524 01528 public function protect() { 01529 $form = new ProtectionForm( $this ); 01530 $form->execute(); 01531 } 01532 01536 public function unprotect() { 01537 $this->protect(); 01538 } 01539 01543 public function delete() { 01544 # This code desperately needs to be totally rewritten 01545 01546 $title = $this->getTitle(); 01547 $user = $this->getContext()->getUser(); 01548 01549 # Check permissions 01550 $permission_errors = $title->getUserPermissionsErrors( 'delete', $user ); 01551 if ( count( $permission_errors ) ) { 01552 throw new PermissionsError( 'delete', $permission_errors ); 01553 } 01554 01555 # Read-only check... 01556 if ( wfReadOnly() ) { 01557 throw new ReadOnlyError; 01558 } 01559 01560 # Better double-check that it hasn't been deleted yet! 01561 $this->mPage->loadPageData( 'fromdbmaster' ); 01562 if ( !$this->mPage->exists() ) { 01563 $deleteLogPage = new LogPage( 'delete' ); 01564 $outputPage = $this->getContext()->getOutput(); 01565 $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) ); 01566 $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>", 01567 array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ) 01568 ); 01569 $outputPage->addHTML( 01570 Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) 01571 ); 01572 LogEventsList::showLogExtract( 01573 $outputPage, 01574 'delete', 01575 $title 01576 ); 01577 01578 return; 01579 } 01580 01581 $request = $this->getContext()->getRequest(); 01582 $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' ); 01583 $deleteReason = $request->getText( 'wpReason' ); 01584 01585 if ( $deleteReasonList == 'other' ) { 01586 $reason = $deleteReason; 01587 } elseif ( $deleteReason != '' ) { 01588 // Entry from drop down menu + additional comment 01589 $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text(); 01590 $reason = $deleteReasonList . $colonseparator . $deleteReason; 01591 } else { 01592 $reason = $deleteReasonList; 01593 } 01594 01595 if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ), 01596 array( 'delete', $this->getTitle()->getPrefixedText() ) ) 01597 ) { 01598 # Flag to hide all contents of the archived revisions 01599 $suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' ); 01600 01601 $this->doDelete( $reason, $suppress ); 01602 01603 WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user ); 01604 01605 return; 01606 } 01607 01608 // Generate deletion reason 01609 $hasHistory = false; 01610 if ( !$reason ) { 01611 try { 01612 $reason = $this->generateReason( $hasHistory ); 01613 } catch ( MWException $e ) { 01614 # if a page is horribly broken, we still want to be able to 01615 # delete it. So be lenient about errors here. 01616 wfDebug( "Error while building auto delete summary: $e" ); 01617 $reason = ''; 01618 } 01619 } 01620 01621 // If the page has a history, insert a warning 01622 if ( $hasHistory ) { 01623 $revisions = $this->mTitle->estimateRevisionCount(); 01624 // @todo FIXME: i18n issue/patchwork message 01625 $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' . 01626 wfMessage( 'historywarning' )->numParams( $revisions )->parse() . 01627 wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title, 01628 wfMessage( 'history' )->escaped(), 01629 array( 'rel' => 'archives' ), 01630 array( 'action' => 'history' ) ) . 01631 '</strong>' 01632 ); 01633 01634 if ( $this->mTitle->isBigDeletion() ) { 01635 global $wgDeleteRevisionsLimit; 01636 $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", 01637 array( 01638 'delete-warning-toobig', 01639 $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit ) 01640 ) 01641 ); 01642 } 01643 } 01644 01645 $this->confirmDelete( $reason ); 01646 } 01647 01653 public function confirmDelete( $reason ) { 01654 wfDebug( "Article::confirmDelete\n" ); 01655 01656 $outputPage = $this->getContext()->getOutput(); 01657 $outputPage->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) ); 01658 $outputPage->addBacklinkSubtitle( $this->getTitle() ); 01659 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 01660 $backlinkCache = $this->getTitle()->getBacklinkCache(); 01661 if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) { 01662 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 01663 'deleting-backlinks-warning' ); 01664 } 01665 $outputPage->addWikiMsg( 'confirmdeletetext' ); 01666 01667 wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) ); 01668 01669 $user = $this->getContext()->getUser(); 01670 01671 if ( $user->isAllowed( 'suppressrevision' ) ) { 01672 $suppress = "<tr id=\"wpDeleteSuppressRow\"> 01673 <td></td> 01674 <td class='mw-input'><strong>" . 01675 Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(), 01676 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) . 01677 "</strong></td> 01678 </tr>"; 01679 } else { 01680 $suppress = ''; 01681 } 01682 $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $this->getTitle() ); 01683 01684 $form = Xml::openElement( 'form', array( 'method' => 'post', 01685 'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) . 01686 Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) . 01687 Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) . 01688 Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) . 01689 "<tr id=\"wpDeleteReasonListRow\"> 01690 <td class='mw-label'>" . 01691 Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) . 01692 "</td> 01693 <td class='mw-input'>" . 01694 Xml::listDropDown( 01695 'wpDeleteReasonList', 01696 wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(), 01697 wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(), 01698 '', 01699 'wpReasonDropDown', 01700 1 01701 ) . 01702 "</td> 01703 </tr> 01704 <tr id=\"wpDeleteReasonRow\"> 01705 <td class='mw-label'>" . 01706 Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) . 01707 "</td> 01708 <td class='mw-input'>" . 01709 Html::input( 'wpReason', $reason, 'text', array( 01710 'size' => '60', 01711 'maxlength' => '255', 01712 'tabindex' => '2', 01713 'id' => 'wpReason', 01714 'autofocus' 01715 ) ) . 01716 "</td> 01717 </tr>"; 01718 01719 # Disallow watching if user is not logged in 01720 if ( $user->isLoggedIn() ) { 01721 $form .= " 01722 <tr> 01723 <td></td> 01724 <td class='mw-input'>" . 01725 Xml::checkLabel( wfMessage( 'watchthis' )->text(), 01726 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) . 01727 "</td> 01728 </tr>"; 01729 } 01730 01731 $form .= " 01732 $suppress 01733 <tr> 01734 <td></td> 01735 <td class='mw-submit'>" . 01736 Xml::submitButton( wfMessage( 'deletepage' )->text(), 01737 array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) . 01738 "</td> 01739 </tr>" . 01740 Xml::closeElement( 'table' ) . 01741 Xml::closeElement( 'fieldset' ) . 01742 Html::hidden( 01743 'wpEditToken', 01744 $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) ) 01745 ) . 01746 Xml::closeElement( 'form' ); 01747 01748 if ( $user->isAllowed( 'editinterface' ) ) { 01749 $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' ); 01750 $link = Linker::link( 01751 $title, 01752 wfMessage( 'delete-edit-reasonlist' )->escaped(), 01753 array(), 01754 array( 'action' => 'edit' ) 01755 ); 01756 $form .= '<p class="mw-delete-editreasons">' . $link . '</p>'; 01757 } 01758 01759 $outputPage->addHTML( $form ); 01760 01761 $deleteLogPage = new LogPage( 'delete' ); 01762 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) ); 01763 LogEventsList::showLogExtract( $outputPage, 'delete', 01764 $this->getTitle() 01765 ); 01766 } 01767 01773 public function doDelete( $reason, $suppress = false ) { 01774 $error = ''; 01775 $outputPage = $this->getContext()->getOutput(); 01776 $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error ); 01777 01778 if ( $status->isGood() ) { 01779 $deleted = $this->getTitle()->getPrefixedText(); 01780 01781 $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) ); 01782 $outputPage->setRobotPolicy( 'noindex,nofollow' ); 01783 01784 $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]'; 01785 01786 $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink ); 01787 $outputPage->returnToMain( false ); 01788 } else { 01789 $outputPage->setPageTitle( 01790 wfMessage( 'cannotdelete-title', 01791 $this->getTitle()->getPrefixedText() ) 01792 ); 01793 01794 if ( $error == '' ) { 01795 $outputPage->addWikiText( 01796 "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>" 01797 ); 01798 $deleteLogPage = new LogPage( 'delete' ); 01799 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) ); 01800 01801 LogEventsList::showLogExtract( 01802 $outputPage, 01803 'delete', 01804 $this->getTitle() 01805 ); 01806 } else { 01807 $outputPage->addHTML( $error ); 01808 } 01809 } 01810 } 01811 01812 /* Caching functions */ 01813 01821 protected function tryFileCache() { 01822 static $called = false; 01823 01824 if ( $called ) { 01825 wfDebug( "Article::tryFileCache(): called twice!?\n" ); 01826 return false; 01827 } 01828 01829 $called = true; 01830 if ( $this->isFileCacheable() ) { 01831 $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'view' ); 01832 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) { 01833 wfDebug( "Article::tryFileCache(): about to load file\n" ); 01834 $cache->loadFromFileCache( $this->getContext() ); 01835 return true; 01836 } else { 01837 wfDebug( "Article::tryFileCache(): starting buffer\n" ); 01838 ob_start( array( &$cache, 'saveToFileCache' ) ); 01839 } 01840 } else { 01841 wfDebug( "Article::tryFileCache(): not cacheable\n" ); 01842 } 01843 01844 return false; 01845 } 01846 01851 public function isFileCacheable() { 01852 $cacheable = false; 01853 01854 if ( HTMLFileCache::useFileCache( $this->getContext() ) ) { 01855 $cacheable = $this->mPage->getID() 01856 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect(); 01857 // Extension may have reason to disable file caching on some pages. 01858 if ( $cacheable ) { 01859 $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) ); 01860 } 01861 } 01862 01863 return $cacheable; 01864 } 01865 01879 public function getParserOutput( $oldid = null, User $user = null ) { 01880 //XXX: bypasses mParserOptions and thus setParserOptions() 01881 01882 if ( $user === null ) { 01883 $parserOptions = $this->getParserOptions(); 01884 } else { 01885 $parserOptions = $this->mPage->makeParserOptions( $user ); 01886 } 01887 01888 return $this->mPage->getParserOutput( $parserOptions, $oldid ); 01889 } 01890 01897 public function setParserOptions( ParserOptions $options ) { 01898 if ( $this->mParserOptions ) { 01899 throw new MWException( "can't change parser options after they have already been set" ); 01900 } 01901 01902 // clone, so if $options is modified later, it doesn't confuse the parser cache. 01903 $this->mParserOptions = clone $options; 01904 } 01905 01910 public function getParserOptions() { 01911 if ( !$this->mParserOptions ) { 01912 $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() ); 01913 } 01914 // Clone to allow modifications of the return value without affecting cache 01915 return clone $this->mParserOptions; 01916 } 01917 01924 public function setContext( $context ) { 01925 $this->mContext = $context; 01926 } 01927 01934 public function getContext() { 01935 if ( $this->mContext instanceof IContextSource ) { 01936 return $this->mContext; 01937 } else { 01938 wfDebug( __METHOD__ . " called and \$mContext is null. " . 01939 "Return RequestContext::getMain(); for sanity\n" ); 01940 return RequestContext::getMain(); 01941 } 01942 } 01943 01948 public function info() { 01949 wfDeprecated( __METHOD__, '1.19' ); 01950 Action::factory( 'info', $this )->show(); 01951 } 01952 01958 public function purge() { 01959 return Action::factory( 'purge', $this )->show(); 01960 } 01961 01966 public function revert() { 01967 wfDeprecated( __METHOD__, '1.19' ); 01968 Action::factory( 'revert', $this )->show(); 01969 } 01970 01975 public function rollback() { 01976 wfDeprecated( __METHOD__, '1.19' ); 01977 Action::factory( 'rollback', $this )->show(); 01978 } 01979 01986 public function __get( $fname ) { 01987 if ( property_exists( $this->mPage, $fname ) ) { 01988 #wfWarn( "Access to raw $fname field " . __CLASS__ ); 01989 return $this->mPage->$fname; 01990 } 01991 trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE ); 01992 } 01993 02001 public function __set( $fname, $fvalue ) { 02002 if ( property_exists( $this->mPage, $fname ) ) { 02003 #wfWarn( "Access to raw $fname field of " . __CLASS__ ); 02004 $this->mPage->$fname = $fvalue; 02005 // Note: extensions may want to toss on new fields 02006 } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) { 02007 $this->mPage->$fname = $fvalue; 02008 } else { 02009 trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE ); 02010 } 02011 } 02012 02021 public function __call( $fname, $args ) { 02022 if ( is_callable( array( $this->mPage, $fname ) ) ) { 02023 #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" ); 02024 return call_user_func_array( array( $this->mPage, $fname ), $args ); 02025 } 02026 trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR ); 02027 } 02028 02029 // ****** B/C functions to work-around PHP silliness with __call and references ****** // 02030 02039 public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, 02040 $reason, User $user 02041 ) { 02042 return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user ); 02043 } 02044 02052 public function updateRestrictions( $limit = array(), $reason = '', 02053 &$cascade = 0, $expiry = array() 02054 ) { 02055 return $this->mPage->doUpdateRestrictions( 02056 $limit, 02057 $expiry, 02058 $cascade, 02059 $reason, 02060 $this->getContext()->getUser() 02061 ); 02062 } 02063 02072 public function doDeleteArticle( $reason, $suppress = false, $id = 0, 02073 $commit = true, &$error = '' 02074 ) { 02075 return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error ); 02076 } 02077 02087 public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) { 02088 $user = is_null( $user ) ? $this->getContext()->getUser() : $user; 02089 return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user ); 02090 } 02091 02100 public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) { 02101 $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser; 02102 return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser ); 02103 } 02104 02109 public function generateReason( &$hasHistory ) { 02110 $title = $this->mPage->getTitle(); 02111 $handler = ContentHandler::getForTitle( $title ); 02112 return $handler->getAutoDeleteReason( $title, $hasHistory ); 02113 } 02114 02115 // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** // 02116 02120 public static function selectFields() { 02121 return WikiPage::selectFields(); 02122 } 02123 02127 public static function onArticleCreate( $title ) { 02128 WikiPage::onArticleCreate( $title ); 02129 } 02130 02134 public static function onArticleDelete( $title ) { 02135 WikiPage::onArticleDelete( $title ); 02136 } 02137 02141 public static function onArticleEdit( $title ) { 02142 WikiPage::onArticleEdit( $title ); 02143 } 02144 02152 public static function getAutosummary( $oldtext, $newtext, $flags ) { 02153 return WikiPage::getAutosummary( $oldtext, $newtext, $flags ); 02154 } 02155 // ****** 02156 }