00001 <?php
00015 class Article {
00019 var $mComment = '';
00020 var $mContent;
00021 var $mContentLoaded = false;
00022 var $mCounter = -1;
00023 var $mCurID = -1;
00024 var $mDataLoaded = false;
00025 var $mForUpdate = false;
00026 var $mGoodAdjustment = 0;
00027 var $mIsRedirect = false;
00028 var $mLatest = false;
00029 var $mMinorEdit;
00030 var $mOldId;
00031 var $mPreparedEdit = false;
00032 var $mRedirectedFrom = null;
00033 var $mRedirectTarget = null;
00034 var $mRedirectUrl = false;
00035 var $mRevIdFetched = 0;
00036 var $mRevision;
00037 var $mTimestamp = '';
00038 var $mTitle;
00039 var $mTotalAdjustment = 0;
00040 var $mTouched = '19700101000000';
00041 var $mUser = -1;
00042 var $mUserText = '';
00043 var $mParserOptions;
00044 var $mParserOutput;
00052 public function __construct( Title $title, $oldId = null ) {
00053 $this->mTitle =& $title;
00054 $this->mOldId = $oldId;
00055 }
00056
00061 public static function newFromID( $id ) {
00062 $t = Title::newFromID( $id );
00063 # FIXME: doesn't inherit right
00064 return $t == null ? null : new self( $t );
00065 # return $t == null ? null : new static( $t ); // PHP 5.3
00066 }
00067
00073 public function setRedirectedFrom( $from ) {
00074 $this->mRedirectedFrom = $from;
00075 }
00076
00084 public function getRedirectTarget() {
00085 if ( !$this->mTitle || !$this->mTitle->isRedirect() )
00086 return null;
00087 if ( !is_null( $this->mRedirectTarget ) )
00088 return $this->mRedirectTarget;
00089 # Query the redirect table
00090 $dbr = wfGetDB( DB_SLAVE );
00091 $row = $dbr->selectRow( 'redirect',
00092 array( 'rd_namespace', 'rd_title' ),
00093 array( 'rd_from' => $this->getID() ),
00094 __METHOD__
00095 );
00096 if ( $row ) {
00097 return $this->mRedirectTarget = Title::makeTitle( $row->rd_namespace, $row->rd_title );
00098 }
00099 # This page doesn't have an entry in the redirect table
00100 return $this->mRedirectTarget = $this->insertRedirect();
00101 }
00102
00109 public function insertRedirect() {
00110 $retval = Title::newFromRedirect( $this->getContent() );
00111 if ( !$retval ) {
00112 return null;
00113 }
00114 $dbw = wfGetDB( DB_MASTER );
00115 $dbw->replace( 'redirect', array( 'rd_from' ),
00116 array(
00117 'rd_from' => $this->getID(),
00118 'rd_namespace' => $retval->getNamespace(),
00119 'rd_title' => $retval->getDBkey()
00120 ),
00121 __METHOD__
00122 );
00123 return $retval;
00124 }
00125
00131 public function followRedirect() {
00132 $text = $this->getContent();
00133 return $this->followRedirectText( $text );
00134 }
00135
00141 public function followRedirectText( $text ) {
00142 $rt = Title::newFromRedirectRecurse( $text );
00143 # process if title object is valid and not special:userlogout
00144 if ( $rt ) {
00145 if ( $rt->getInterwiki() != '' ) {
00146 if ( $rt->isLocal() ) {
00147
00148
00149
00150
00151 $source = $this->mTitle->getFullURL( 'redirect=no' );
00152 return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
00153 }
00154 } else {
00155 if ( $rt->getNamespace() == NS_SPECIAL ) {
00156
00157
00158
00159
00160
00161 if ( $rt->isSpecial( 'Userlogout' ) ) {
00162
00163 } else {
00164 return $rt->getFullURL();
00165 }
00166 }
00167 return $rt;
00168 }
00169 }
00170
00171 return false;
00172 }
00173
00177 public function getTitle() {
00178 return $this->mTitle;
00179 }
00180
00185 public function clear() {
00186 $this->mDataLoaded = false;
00187 $this->mContentLoaded = false;
00188
00189 $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded
00190 $this->mRedirectedFrom = null; # Title object if set
00191 $this->mRedirectTarget = null; # Title object if set
00192 $this->mUserText =
00193 $this->mTimestamp = $this->mComment = '';
00194 $this->mGoodAdjustment = $this->mTotalAdjustment = 0;
00195 $this->mTouched = '19700101000000';
00196 $this->mForUpdate = false;
00197 $this->mIsRedirect = false;
00198 $this->mRevIdFetched = 0;
00199 $this->mRedirectUrl = false;
00200 $this->mLatest = false;
00201 $this->mPreparedEdit = false;
00202 }
00203
00211 public function getContent() {
00212 global $wgUser, $wgContLang, $wgOut, $wgMessageCache;
00213 wfProfileIn( __METHOD__ );
00214 if ( $this->getID() === 0 ) {
00215 # If this is a MediaWiki:x message, then load the messages
00216 # and return the message value for x.
00217 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
00218 # If this is a system message, get the default text.
00219 list( $message, $lang ) = $wgMessageCache->figureMessage( $wgContLang->lcfirst( $this->mTitle->getText() ) );
00220 $wgMessageCache->loadAllMessages( $lang );
00221 $text = wfMsgGetKey( $message, false, $lang, false );
00222 if ( wfEmptyMsg( $message, $text ) )
00223 $text = '';
00224 } else {
00225 $text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' );
00226 }
00227 wfProfileOut( __METHOD__ );
00228 return $text;
00229 } else {
00230 $this->loadContent();
00231 wfProfileOut( __METHOD__ );
00232 return $this->mContent;
00233 }
00234 }
00235
00241 public function getRawText() {
00242
00243 if ( $this->mContentLoaded && $this->mOldId == 0 ) {
00244 return $this->mContent;
00245 }
00246 $rev = Revision::newFromTitle( $this->mTitle );
00247 $text = $rev ? $rev->getRawText() : false;
00248 return $text;
00249 }
00250
00263 public function getSection( $text, $section ) {
00264 global $wgParser;
00265 return $wgParser->getSection( $text, $section );
00266 }
00267
00276 public function getUndoText( Revision $undo, Revision $undoafter = null ) {
00277 $undo_text = $undo->getText();
00278 $undoafter_text = $undoafter->getText();
00279 $cur_text = $this->getContent();
00280 if ( $cur_text == $undo_text ) {
00281 # No use doing a merge if it's just a straight revert.
00282 return $undoafter_text;
00283 }
00284 $undone_text = '';
00285 if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) )
00286 return false;
00287 return $undone_text;
00288 }
00289
00294 public function getOldID() {
00295 if ( is_null( $this->mOldId ) ) {
00296 $this->mOldId = $this->getOldIDFromRequest();
00297 }
00298 return $this->mOldId;
00299 }
00300
00306 public function getOldIDFromRequest() {
00307 global $wgRequest;
00308 $this->mRedirectUrl = false;
00309 $oldid = $wgRequest->getVal( 'oldid' );
00310 if ( isset( $oldid ) ) {
00311 $oldid = intval( $oldid );
00312 if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
00313 $nextid = $this->mTitle->getNextRevisionID( $oldid );
00314 if ( $nextid ) {
00315 $oldid = $nextid;
00316 } else {
00317 $this->mRedirectUrl = $this->mTitle->getFullURL( 'redirect=no' );
00318 }
00319 } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
00320 $previd = $this->mTitle->getPreviousRevisionID( $oldid );
00321 if ( $previd ) {
00322 $oldid = $previd;
00323 }
00324 }
00325 }
00326 if ( !$oldid ) {
00327 $oldid = 0;
00328 }
00329 return $oldid;
00330 }
00331
00335 function loadContent() {
00336 if ( $this->mContentLoaded ) return;
00337 wfProfileIn( __METHOD__ );
00338 # Query variables :P
00339 $oldid = $this->getOldID();
00340 # Pre-fill content with error message so that if something
00341 # fails we'll have something telling us what we intended.
00342 $this->mOldId = $oldid;
00343 $this->fetchContent( $oldid );
00344 wfProfileOut( __METHOD__ );
00345 }
00346
00347
00353 protected function pageData( $dbr, $conditions ) {
00354 $fields = array(
00355 'page_id',
00356 'page_namespace',
00357 'page_title',
00358 'page_restrictions',
00359 'page_counter',
00360 'page_is_redirect',
00361 'page_is_new',
00362 'page_random',
00363 'page_touched',
00364 'page_latest',
00365 'page_len',
00366 );
00367 wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
00368 $row = $dbr->selectRow(
00369 'page',
00370 $fields,
00371 $conditions,
00372 __METHOD__
00373 );
00374 wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
00375 return $row ;
00376 }
00377
00382 public function pageDataFromTitle( $dbr, $title ) {
00383 return $this->pageData( $dbr, array(
00384 'page_namespace' => $title->getNamespace(),
00385 'page_title' => $title->getDBkey() ) );
00386 }
00387
00392 protected function pageDataFromId( $dbr, $id ) {
00393 return $this->pageData( $dbr, array( 'page_id' => $id ) );
00394 }
00395
00402 public function loadPageData( $data = 'fromdb' ) {
00403 if ( $data === 'fromdb' ) {
00404 $dbr = wfGetDB( DB_MASTER );
00405 $data = $this->pageDataFromId( $dbr, $this->getId() );
00406 }
00407
00408 $lc = LinkCache::singleton();
00409 if ( $data ) {
00410 $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect );
00411
00412 $this->mTitle->mArticleID = intval( $data->page_id );
00413
00414 # Old-fashioned restrictions
00415 $this->mTitle->loadRestrictions( $data->page_restrictions );
00416
00417 $this->mCounter = intval( $data->page_counter );
00418 $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
00419 $this->mIsRedirect = intval( $data->page_is_redirect );
00420 $this->mLatest = intval( $data->page_latest );
00421 } else {
00422 if ( is_object( $this->mTitle ) ) {
00423 $lc->addBadLinkObj( $this->mTitle );
00424 }
00425 $this->mTitle->mArticleID = 0;
00426 }
00427
00428 $this->mDataLoaded = true;
00429 }
00430
00437 function fetchContent( $oldid = 0 ) {
00438 if ( $this->mContentLoaded ) {
00439 return $this->mContent;
00440 }
00441
00442 $dbr = wfGetDB( DB_MASTER );
00443
00444 # Pre-fill content with error message so that if something
00445 # fails we'll have something telling us what we intended.
00446 $t = $this->mTitle->getPrefixedText();
00447 $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
00448 $this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
00449
00450 if ( $oldid ) {
00451 $revision = Revision::newFromId( $oldid );
00452 if ( is_null( $revision ) ) {
00453 wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
00454 return false;
00455 }
00456 $data = $this->pageDataFromId( $dbr, $revision->getPage() );
00457 if ( !$data ) {
00458 wfDebug( __METHOD__ . " failed to get page data linked to revision id $oldid\n" );
00459 return false;
00460 }
00461 $this->mTitle = Title::makeTitle( $data->page_namespace, $data->page_title );
00462 $this->loadPageData( $data );
00463 } else {
00464 if ( !$this->mDataLoaded ) {
00465 $data = $this->pageDataFromTitle( $dbr, $this->mTitle );
00466 if ( !$data ) {
00467 wfDebug( __METHOD__ . " failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" );
00468 return false;
00469 }
00470 $this->loadPageData( $data );
00471 }
00472 $revision = Revision::newFromId( $this->mLatest );
00473 if ( is_null( $revision ) ) {
00474 wfDebug( __METHOD__ . " failed to retrieve current page, rev_id {$this->mLatest}\n" );
00475 return false;
00476 }
00477 }
00478
00479
00480
00481 $this->mContent = $revision->getText( Revision::FOR_THIS_USER );
00482
00483 $this->mUser = $revision->getUser();
00484 $this->mUserText = $revision->getUserText();
00485 $this->mComment = $revision->getComment();
00486 $this->mTimestamp = wfTimestamp( TS_MW, $revision->getTimestamp() );
00487
00488 $this->mRevIdFetched = $revision->getId();
00489 $this->mContentLoaded = true;
00490 $this->mRevision =& $revision;
00491
00492 wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ) ;
00493
00494 return $this->mContent;
00495 }
00496
00502 public function forUpdate( $x = null ) {
00503 return wfSetVar( $this->mForUpdate, $x );
00504 }
00505
00512 function getDB() {
00513 wfDeprecated( __METHOD__ );
00514 return wfGetDB( DB_MASTER );
00515 }
00516
00524 protected function getSelectOptions( $options = '' ) {
00525 if ( $this->mForUpdate ) {
00526 if ( is_array( $options ) ) {
00527 $options[] = 'FOR UPDATE';
00528 } else {
00529 $options = 'FOR UPDATE';
00530 }
00531 }
00532 return $options;
00533 }
00534
00538 public function getID() {
00539 if ( $this->mTitle ) {
00540 return $this->mTitle->getArticleID();
00541 } else {
00542 return 0;
00543 }
00544 }
00545
00549 public function exists() {
00550 return $this->getId() > 0;
00551 }
00552
00561 public function hasViewableContent() {
00562 return $this->exists() || $this->mTitle->isAlwaysKnown();
00563 }
00564
00568 public function getCount() {
00569 if ( -1 == $this->mCounter ) {
00570 $id = $this->getID();
00571 if ( $id == 0 ) {
00572 $this->mCounter = 0;
00573 } else {
00574 $dbr = wfGetDB( DB_SLAVE );
00575 $this->mCounter = $dbr->selectField( 'page',
00576 'page_counter',
00577 array( 'page_id' => $id ),
00578 __METHOD__,
00579 $this->getSelectOptions()
00580 );
00581 }
00582 }
00583 return $this->mCounter;
00584 }
00585
00593 public function isCountable( $text ) {
00594 global $wgUseCommaCount;
00595
00596 $token = $wgUseCommaCount ? ',' : '[[';
00597 return $this->mTitle->isContentPage() && !$this->isRedirect( $text ) && in_string( $token, $text );
00598 }
00599
00606 public function isRedirect( $text = false ) {
00607 if ( $text === false ) {
00608 if ( $this->mDataLoaded ) {
00609 return $this->mIsRedirect;
00610 }
00611
00612 $this->loadContent();
00613 $titleObj = Title::newFromRedirectRecurse( $this->fetchContent() );
00614 } else {
00615 $titleObj = Title::newFromRedirect( $text );
00616 }
00617 return $titleObj !== null;
00618 }
00619
00625 public function isCurrent() {
00626 # If no oldid, this is the current version.
00627 if ( $this->getOldID() == 0 ) {
00628 return true;
00629 }
00630 return $this->exists() && isset( $this->mRevision ) && $this->mRevision->isCurrent();
00631 }
00632
00637 protected function loadLastEdit() {
00638 if ( -1 != $this->mUser )
00639 return;
00640
00641 # New or non-existent articles have no user information
00642 $id = $this->getID();
00643 if ( 0 == $id ) return;
00644
00645 $this->mLastRevision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id );
00646 if ( !is_null( $this->mLastRevision ) ) {
00647 $this->mUser = $this->mLastRevision->getUser();
00648 $this->mUserText = $this->mLastRevision->getUserText();
00649 $this->mTimestamp = $this->mLastRevision->getTimestamp();
00650 $this->mComment = $this->mLastRevision->getComment();
00651 $this->mMinorEdit = $this->mLastRevision->isMinor();
00652 $this->mRevIdFetched = $this->mLastRevision->getId();
00653 }
00654 }
00655
00656 public function getTimestamp() {
00657
00658 if ( !$this->mTimestamp ) {
00659 $this->loadLastEdit();
00660 }
00661 return wfTimestamp( TS_MW, $this->mTimestamp );
00662 }
00663
00664 public function getUser() {
00665 $this->loadLastEdit();
00666 return $this->mUser;
00667 }
00668
00669 public function getUserText() {
00670 $this->loadLastEdit();
00671 return $this->mUserText;
00672 }
00673
00674 public function getComment() {
00675 $this->loadLastEdit();
00676 return $this->mComment;
00677 }
00678
00679 public function getMinorEdit() {
00680 $this->loadLastEdit();
00681 return $this->mMinorEdit;
00682 }
00683
00684
00685 public function getRevIdFetched() {
00686 $this->loadLastEdit();
00687 return $this->mRevIdFetched;
00688 }
00689
00694 public function getContributors( $limit = 0, $offset = 0 ) {
00695 # XXX: this is expensive; cache this info somewhere.
00696
00697 $dbr = wfGetDB( DB_SLAVE );
00698 $revTable = $dbr->tableName( 'revision' );
00699 $userTable = $dbr->tableName( 'user' );
00700
00701 $pageId = $this->getId();
00702
00703 $user = $this->getUser();
00704 if ( $user ) {
00705 $excludeCond = "AND rev_user != $user";
00706 } else {
00707 $userText = $dbr->addQuotes( $this->getUserText() );
00708 $excludeCond = "AND rev_user_text != $userText";
00709 }
00710
00711 $deletedBit = $dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER );
00712
00713 $sql = "SELECT {$userTable}.*, rev_user_text as user_name, MAX(rev_timestamp) as timestamp
00714 FROM $revTable LEFT JOIN $userTable ON rev_user = user_id
00715 WHERE rev_page = $pageId
00716 $excludeCond
00717 AND $deletedBit = 0
00718 GROUP BY rev_user, rev_user_text
00719 ORDER BY timestamp DESC";
00720
00721 if ( $limit > 0 )
00722 $sql = $dbr->limitResult( $sql, $limit, $offset );
00723
00724 $sql .= ' ' . $this->getSelectOptions();
00725 $res = $dbr->query( $sql, __METHOD__ );
00726
00727 return new UserArrayFromResult( $res );
00728 }
00729
00734 public function view() {
00735 global $wgUser, $wgOut, $wgRequest, $wgContLang;
00736 global $wgEnableParserCache, $wgStylePath, $wgParser;
00737 global $wgUseTrackbacks, $wgUseFileCache;
00738
00739 wfProfileIn( __METHOD__ );
00740
00741 # Get variables from query string
00742 $oldid = $this->getOldID();
00743 $parserCache = ParserCache::singleton();
00744
00745 $parserOptions = clone $this->getParserOptions();
00746 # Render printable version, use printable version cache
00747 if ( $wgOut->isPrintable() ) {
00748 $parserOptions->setIsPrintable( true );
00749 }
00750
00751 # Try client and file cache
00752 if ( $oldid === 0 && $this->checkTouched() ) {
00753 global $wgUseETag;
00754 if ( $wgUseETag ) {
00755 $wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) );
00756 }
00757 # Is is client cached?
00758 if ( $wgOut->checkLastModified( $this->getTouched() ) ) {
00759 wfDebug( __METHOD__ . ": done 304\n" );
00760 wfProfileOut( __METHOD__ );
00761 return;
00762 # Try file cache
00763 } else if ( $wgUseFileCache && $this->tryFileCache() ) {
00764 wfDebug( __METHOD__ . ": done file cache\n" );
00765 # tell wgOut that output is taken care of
00766 $wgOut->disable();
00767 $this->viewUpdates();
00768 wfProfileOut( __METHOD__ );
00769 return;
00770 }
00771 }
00772
00773 $sk = $wgUser->getSkin();
00774
00775 # getOldID may want us to redirect somewhere else
00776 if ( $this->mRedirectUrl ) {
00777 $wgOut->redirect( $this->mRedirectUrl );
00778 wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
00779 wfProfileOut( __METHOD__ );
00780 return;
00781 }
00782
00783 $wgOut->setArticleFlag( true );
00784 # Set page title (may be overridden by DISPLAYTITLE)
00785 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
00786
00787 # If we got diff in the query, we want to see a diff page instead of the article.
00788 if ( !is_null( $wgRequest->getVal( 'diff' ) ) ) {
00789 wfDebug( __METHOD__ . ": showing diff page\n" );
00790 $this->showDiffPage();
00791 wfProfileOut( __METHOD__ );
00792 return;
00793 }
00794
00795 # Allow frames by default
00796 $wgOut->allowClickjacking();
00797
00798 # Should the parser cache be used?
00799 $useParserCache = $this->useParserCache( $oldid );
00800 wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
00801 if ( $wgUser->getOption( 'stubthreshold' ) ) {
00802 wfIncrStats( 'pcache_miss_stub' );
00803 }
00804
00805 $wasRedirected = $this->showRedirectedFromHeader();
00806 $this->showNamespaceHeader();
00807
00808 # Iterate through the possible ways of constructing the output text.
00809 # Keep going until $outputDone is set, or we run out of things to do.
00810 $pass = 0;
00811 $outputDone = false;
00812 $this->mParserOutput = false;
00813 while ( !$outputDone && ++$pass ) {
00814 switch( $pass ) {
00815 case 1:
00816 wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
00817 break;
00818
00819 case 2:
00820 # Try the parser cache
00821 if ( $useParserCache ) {
00822 $this->mParserOutput = $parserCache->get( $this, $parserOptions );
00823 if ( $this->mParserOutput !== false ) {
00824 wfDebug( __METHOD__ . ": showing parser cache contents\n" );
00825 $wgOut->addParserOutput( $this->mParserOutput );
00826 # Ensure that UI elements requiring revision ID have
00827 # the correct version information.
00828 $wgOut->setRevisionId( $this->mLatest );
00829 $outputDone = true;
00830 }
00831 }
00832 break;
00833
00834 case 3:
00835 $text = $this->getContent();
00836 if ( $text === false || $this->getID() == 0 ) {
00837 wfDebug( __METHOD__ . ": showing missing article\n" );
00838 $this->showMissingArticle();
00839 wfProfileOut( __METHOD__ );
00840 return;
00841 }
00842
00843 # Another whitelist check in case oldid is altering the title
00844 if ( !$this->mTitle->userCanRead() ) {
00845 wfDebug( __METHOD__ . ": denied on secondary read check\n" );
00846 $wgOut->loginToUse();
00847 $wgOut->output();
00848 $wgOut->disable();
00849 wfProfileOut( __METHOD__ );
00850 return;
00851 }
00852
00853 # Are we looking at an old revision
00854 if ( $oldid && !is_null( $this->mRevision ) ) {
00855 $this->setOldSubtitle( $oldid );
00856 if ( !$this->showDeletedRevisionHeader() ) {
00857 wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
00858 wfProfileOut( __METHOD__ );
00859 return;
00860 }
00861 # If this "old" version is the current, then try the parser cache...
00862 if ( $oldid === $this->getLatest() && $this->useParserCache( false ) ) {
00863 $this->mParserOutput = $parserCache->get( $this, $parserOptions );
00864 if ( $this->mParserOutput ) {
00865 wfDebug( __METHOD__ . ": showing parser cache for current rev permalink\n" );
00866 $wgOut->addParserOutput( $this->mParserOutput );
00867 $wgOut->setRevisionId( $this->mLatest );
00868 $this->showViewFooter();
00869 $this->viewUpdates();
00870 wfProfileOut( __METHOD__ );
00871 return;
00872 }
00873 }
00874 }
00875
00876 # Ensure that UI elements requiring revision ID have
00877 # the correct version information.
00878 $wgOut->setRevisionId( $this->getRevIdFetched() );
00879
00880 # Pages containing custom CSS or JavaScript get special treatment
00881 if ( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
00882 wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
00883 $this->showCssOrJsPage();
00884 $outputDone = true;
00885 } else if ( $rt = Title::newFromRedirectArray( $text ) ) {
00886 wfDebug( __METHOD__ . ": showing redirect=no page\n" );
00887 # Viewing a redirect page (e.g. with parameter redirect=no)
00888 # Don't append the subtitle if this was an old revision
00889 $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
00890 # Parse just to get categories, displaytitle, etc.
00891 $this->mParserOutput = $wgParser->parse( $text, $this->mTitle, $parserOptions );
00892 $wgOut->addParserOutputNoText( $this->mParserOutput );
00893 $outputDone = true;
00894 }
00895 break;
00896
00897 case 4:
00898 # Run the parse, protected by a pool counter
00899 wfDebug( __METHOD__ . ": doing uncached parse\n" );
00900 $key = $parserCache->getKey( $this, $parserOptions );
00901 $poolCounter = PoolCounter::factory( 'Article::view', $key );
00902 $dirtyCallback = $useParserCache ? array( $this, 'tryDirtyCache' ) : false;
00903 $status = $poolCounter->executeProtected( array( $this, 'doViewParse' ), $dirtyCallback );
00904
00905 if ( !$status->isOK() ) {
00906 # Connection or timeout error
00907 $this->showPoolError( $status );
00908 wfProfileOut( __METHOD__ );
00909 return;
00910 } else {
00911 $outputDone = true;
00912 }
00913 break;
00914
00915 # Should be unreachable, but just in case...
00916 default:
00917 break 2;
00918 }
00919 }
00920
00921 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
00922 if ( $this->mParserOutput ) {
00923 $titleText = $this->mParserOutput->getTitleText();
00924 if ( strval( $titleText ) !== '' ) {
00925 $wgOut->setPageTitle( $titleText );
00926 }
00927 }
00928
00929 # For the main page, overwrite the <title> element with the con-
00930 # tents of 'pagetitle-view-mainpage' instead of the default (if
00931 # that's not empty).
00932 if ( $this->mTitle->equals( Title::newMainPage() )
00933 && ( $m = wfMsgForContent( 'pagetitle-view-mainpage' ) ) !== '' )
00934 {
00935 $wgOut->setHTMLTitle( $m );
00936 }
00937
00938 # Now that we've filled $this->mParserOutput, we know whether
00939 # there are any __NOINDEX__ tags on the page
00940 $policy = $this->getRobotPolicy( 'view' );
00941 $wgOut->setIndexPolicy( $policy['index'] );
00942 $wgOut->setFollowPolicy( $policy['follow'] );
00943
00944 $this->showViewFooter();
00945 $this->viewUpdates();
00946 wfProfileOut( __METHOD__ );
00947 }
00948
00953 public function showDiffPage() {
00954 global $wgOut, $wgRequest, $wgUser;
00955
00956 $diff = $wgRequest->getVal( 'diff' );
00957 $rcid = $wgRequest->getVal( 'rcid' );
00958 $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
00959 $purge = $wgRequest->getVal( 'action' ) == 'purge';
00960 $unhide = $wgRequest->getInt( 'unhide' ) == 1;
00961 $oldid = $this->getOldID();
00962
00963 $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $unhide );
00964
00965 $this->mRevIdFetched = $de->mNewid;
00966 $de->showDiffPage( $diffOnly );
00967
00968
00969 $this->loadPageData();
00970 if ( $diff == 0 || $diff == $this->mLatest ) {
00971 # Run view updates for current revision only
00972 $this->viewUpdates();
00973 }
00974 }
00975
00983 public function showCssOrJsPage() {
00984 global $wgOut;
00985 $wgOut->addHTML( wfMsgExt( 'clearyourcache', 'parse' ) );
00986
00987 if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) {
00988
00989 $m = array();
00990 preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
00991 $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
00992 $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
00993 $wgOut->addHTML( "\n</pre>\n" );
00994 }
00995 }
00996
01003 public function getRobotPolicyForView() {
01004 wfDeprecated( __FUNC__ );
01005 $policy = $this->getRobotPolicy( 'view' );
01006 return $policy['index'] . ',' . $policy['follow'];
01007 }
01008
01015 public function getRobotPolicy( $action ) {
01016
01017 global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies;
01018 global $wgDefaultRobotPolicy, $wgRequest;
01019
01020 $ns = $this->mTitle->getNamespace();
01021 if ( $ns == NS_USER || $ns == NS_USER_TALK ) {
01022 # Don't index user and user talk pages for blocked users (bug 11443)
01023 if ( !$this->mTitle->isSubpage() ) {
01024 $block = new Block();
01025 if ( $block->load( $this->mTitle->getText() ) ) {
01026 return array( 'index' => 'noindex',
01027 'follow' => 'nofollow' );
01028 }
01029 }
01030 }
01031
01032 if ( $this->getID() === 0 || $this->getOldID() ) {
01033 # Non-articles (special pages etc), and old revisions
01034 return array( 'index' => 'noindex',
01035 'follow' => 'nofollow' );
01036 } elseif ( $wgOut->isPrintable() ) {
01037 # Discourage indexing of printable versions, but encourage following
01038 return array( 'index' => 'noindex',
01039 'follow' => 'follow' );
01040 } elseif ( $wgRequest->getInt( 'curid' ) ) {
01041 # For ?curid=x urls, disallow indexing
01042 return array( 'index' => 'noindex',
01043 'follow' => 'follow' );
01044 }
01045
01046 # Otherwise, construct the policy based on the various config variables.
01047 $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
01048
01049 if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
01050 # Honour customised robot policies for this namespace
01051 $policy = array_merge( $policy,
01052 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] ) );
01053 }
01054 if ( $this->mTitle->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) {
01055 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
01056 # a final sanity check that we have really got the parser output.
01057 $policy = array_merge( $policy,
01058 array( 'index' => $this->mParserOutput->getIndexPolicy() ) );
01059 }
01060
01061 if ( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) {
01062 # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
01063 $policy = array_merge( $policy,
01064 self::formatRobotPolicy( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) );
01065 }
01066
01067 return $policy;
01068
01069 }
01070
01078 public static function formatRobotPolicy( $policy ) {
01079 if ( is_array( $policy ) ) {
01080 return $policy;
01081 } elseif ( !$policy ) {
01082 return array();
01083 }
01084
01085 $policy = explode( ',', $policy );
01086 $policy = array_map( 'trim', $policy );
01087
01088 $arr = array();
01089 foreach ( $policy as $var ) {
01090 if ( in_array( $var, array( 'index', 'noindex' ) ) ) {
01091 $arr['index'] = $var;
01092 } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) {
01093 $arr['follow'] = $var;
01094 }
01095 }
01096 return $arr;
01097 }
01098
01104 public function showRedirectedFromHeader() {
01105 global $wgOut, $wgUser, $wgRequest, $wgRedirectSources;
01106
01107 $rdfrom = $wgRequest->getVal( 'rdfrom' );
01108 $sk = $wgUser->getSkin();
01109 if ( isset( $this->mRedirectedFrom ) ) {
01110
01111
01112 if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
01113 $redir = $sk->link(
01114 $this->mRedirectedFrom,
01115 null,
01116 array(),
01117 array( 'redirect' => 'no' ),
01118 array( 'known', 'noclasses' )
01119 );
01120 $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
01121 $wgOut->setSubtitle( $s );
01122
01123
01124 if ( strval( $this->mTitle->getFragment() ) != '' ) {
01125 $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() );
01126 $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" );
01127 }
01128
01129
01130 $wgOut->addLink( array( 'rel' => 'canonical',
01131 'href' => $this->mTitle->getLocalURL() )
01132 );
01133 return true;
01134 }
01135 } elseif ( $rdfrom ) {
01136
01137
01138 if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
01139 $redir = $sk->makeExternalLink( $rdfrom, $rdfrom );
01140 $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
01141 $wgOut->setSubtitle( $s );
01142 return true;
01143 }
01144 }
01145 return false;
01146 }
01147
01152 public function showNamespaceHeader() {
01153 global $wgOut;
01154 if ( $this->mTitle->isTalkPage() ) {
01155 $msg = wfMsgNoTrans( 'talkpageheader' );
01156 if ( $msg !== '-' && !wfEmptyMsg( 'talkpageheader', $msg ) ) {
01157 $wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1</div>", array( 'talkpageheader' ) );
01158 }
01159 }
01160 }
01161
01165 public function showViewFooter() {
01166 global $wgOut, $wgUseTrackbacks, $wgRequest;
01167 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
01168 if ( $this->mTitle->getNamespace() == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) {
01169 $wgOut->addWikiMsg( 'anontalkpagetext' );
01170 }
01171
01172 # If we have been passed an &rcid= parameter, we want to give the user a
01173 # chance to mark this new article as patrolled.
01174 $this->showPatrolFooter();
01175
01176 # Trackbacks
01177 if ( $wgUseTrackbacks ) {
01178 $this->addTrackbacks();
01179 }
01180 }
01181
01187 public function showPatrolFooter() {
01188 global $wgOut, $wgRequest, $wgUser;
01189 $rcid = $wgRequest->getVal( 'rcid' );
01190
01191 if ( !$rcid || !$this->mTitle->exists() || !$this->mTitle->quickUserCan( 'patrol' ) ) {
01192 return;
01193 }
01194
01195 $sk = $wgUser->getSkin();
01196
01197 $wgOut->addHTML(
01198 "<div class='patrollink'>" .
01199 wfMsgHtml(
01200 'markaspatrolledlink',
01201 $sk->link(
01202 $this->mTitle,
01203 wfMsgHtml( 'markaspatrolledtext' ),
01204 array(),
01205 array(
01206 'action' => 'markpatrolled',
01207 'rcid' => $rcid
01208 ),
01209 array( 'known', 'noclasses' )
01210 )
01211 ) .
01212 '</div>'
01213 );
01214 }
01215
01220 public function showMissingArticle() {
01221 global $wgOut, $wgRequest, $wgUser;
01222
01223 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
01224 if ( $this->mTitle->getNamespace() == NS_USER || $this->mTitle->getNamespace() == NS_USER_TALK ) {
01225 $parts = explode( '/', $this->mTitle->getText() );
01226 $rootPart = $parts[0];
01227 $user = User::newFromName( $rootPart, false );
01228 $ip = User::isIP( $rootPart );
01229 if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
01230 $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1</div>",
01231 array( 'userpage-userdoesnotexist-view', $rootPart ) );
01232 } else if ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
01233 LogEventsList::showLogExtract(
01234 $wgOut,
01235 'block',
01236 $user->getUserPage()->getPrefixedText(),
01237 '',
01238 array(
01239 'lim' => 1,
01240 'showIfEmpty' => false,
01241 'msgKey' => array(
01242 'blocked-notice-logextract',
01243 $user->getName() # Support GENDER in notice
01244 )
01245 )
01246 );
01247 }
01248 }
01249 wfRunHooks( 'ShowMissingArticle', array( $this ) );
01250 # Show delete and move logs
01251 LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle->getPrefixedText(), '',
01252 array( 'lim' => 10,
01253 'conds' => array( "log_action != 'revision'" ),
01254 'showIfEmpty' => false,
01255 'msgKey' => array( 'moveddeleted-notice' ) )
01256 );
01257
01258 # Show error message
01259 $oldid = $this->getOldID();
01260 if ( $oldid ) {
01261 $text = wfMsgNoTrans( 'missing-article',
01262 $this->mTitle->getPrefixedText(),
01263 wfMsgNoTrans( 'missingarticle-rev', $oldid ) );
01264 } elseif ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) {
01265
01266 $text = $this->getContent();
01267 } else {
01268 $createErrors = $this->mTitle->getUserPermissionsErrors( 'create', $wgUser );
01269 $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
01270 $errors = array_merge( $createErrors, $editErrors );
01271
01272 if ( !count( $errors ) )
01273 $text = wfMsgNoTrans( 'noarticletext' );
01274 else
01275 $text = wfMsgNoTrans( 'noarticletext-nopermission' );
01276 }
01277 $text = "<div class='noarticletext'>\n$text\n</div>";
01278 if ( !$this->hasViewableContent() ) {
01279
01280
01281 $wgRequest->response()->header( "HTTP/1.x 404 Not Found" );
01282 }
01283 $wgOut->addWikiText( $text );
01284 }
01285
01291 public function showDeletedRevisionHeader() {
01292 global $wgOut, $wgRequest;
01293 if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
01294
01295 return true;
01296 }
01297
01298 if ( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) {
01299 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
01300 'rev-deleted-text-permission' );
01301 return false;
01302
01303 } else if ( $wgRequest->getInt( 'unhide' ) != 1 ) {
01304 # Give explanation and add a link to view the revision...
01305 $oldid = intval( $this->getOldID() );
01306 $link = $this->mTitle->getFullUrl( "oldid={$oldid}&unhide=1" );
01307 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
01308 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
01309 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
01310 array( $msg, $link ) );
01311 return false;
01312
01313 } else {
01314 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
01315 'rev-suppressed-text-view' : 'rev-deleted-text-view';
01316 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", $msg );
01317 return true;
01318 }
01319 }
01320
01321
01322
01323
01324 public function useParserCache( $oldid ) {
01325 global $wgUser, $wgEnableParserCache;
01326
01327 return $wgEnableParserCache
01328 && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0
01329 && $this->exists()
01330 && empty( $oldid )
01331 && !$this->mTitle->isCssOrJsPage()
01332 && !$this->mTitle->isCssJsSubpage();
01333 }
01334
01338 public function doViewParse() {
01339 global $wgOut;
01340 $oldid = $this->getOldID();
01341 $useParserCache = $this->useParserCache( $oldid );
01342 $parserOptions = clone $this->getParserOptions();
01343 # Render printable version, use printable version cache
01344 $parserOptions->setIsPrintable( $wgOut->isPrintable() );
01345 # Don't show section-edit links on old revisions... this way lies madness.
01346 $parserOptions->setEditSection( $this->isCurrent() );
01347 $useParserCache = $this->useParserCache( $oldid );
01348 $this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions );
01349 }
01350
01357 public function tryDirtyCache() {
01358 global $wgOut;
01359 $parserCache = ParserCache::singleton();
01360 $options = $this->getParserOptions();
01361 $options->setIsPrintable( $wgOut->isPrintable() );
01362 $output = $parserCache->getDirty( $this, $options );
01363 if ( $output ) {
01364 wfDebug( __METHOD__ . ": sending dirty output\n" );
01365 wfDebugLog( 'dirty', "dirty output " . $parserCache->getKey( $this, $options ) . "\n" );
01366 $wgOut->setSquidMaxage( 0 );
01367 $this->mParserOutput = $output;
01368 $wgOut->addParserOutput( $output );
01369 $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
01370 return true;
01371 } else {
01372 wfDebugLog( 'dirty', "dirty missing\n" );
01373 wfDebug( __METHOD__ . ": no dirty cache\n" );
01374 return false;
01375 }
01376 }
01377
01382 public function showPoolError( $status ) {
01383 global $wgOut;
01384 $wgOut->clearHTML();
01385 $wgOut->enableClientCache( false );
01386 $wgOut->setRobotPolicy( 'noindex,nofollow' );
01387 $wgOut->addWikiText(
01388 '<div class="errorbox">' .
01389 $status->getWikiText( false, 'view-pool-error' ) .
01390 '</div>'
01391 );
01392 }
01393
01400 public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
01401 global $wgOut, $wgContLang, $wgStylePath, $wgUser;
01402 # Display redirect
01403 if ( !is_array( $target ) ) {
01404 $target = array( $target );
01405 }
01406 $imageDir = $wgContLang->getDir();
01407 $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
01408 $imageUrl2 = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
01409 $alt2 = $wgContLang->isRTL() ? '←' : '→';
01410
01411 if ( $appendSubtitle ) {
01412 $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
01413 }
01414 $sk = $wgUser->getSkin();
01415
01416 $title = array_shift( $target );
01417 if ( $forceKnown ) {
01418 $link = $sk->link(
01419 $title,
01420 htmlspecialchars( $title->getFullText() ),
01421 array(),
01422 array(),
01423 array( 'known', 'noclasses' )
01424 );
01425 } else {
01426 $link = $sk->link( $title, htmlspecialchars( $title->getFullText() ) );
01427 }
01428
01429 foreach ( $target as $rt ) {
01430 if ( $forceKnown ) {
01431 $link .= '<img src="' . $imageUrl2 . '" alt="' . $alt2 . ' " />'
01432 . $sk->link(
01433 $rt,
01434 htmlspecialchars( $rt->getFullText() ),
01435 array(),
01436 array(),
01437 array( 'known', 'noclasses' )
01438 );
01439 } else {
01440 $link .= '<img src="' . $imageUrl2 . '" alt="' . $alt2 . ' " />'
01441 . $sk->link( $rt, htmlspecialchars( $rt->getFullText() ) );
01442 }
01443 }
01444 return '<img src="' . $imageUrl . '" alt="#REDIRECT " />' .
01445 '<span class="redirectText">' . $link . '</span>';
01446
01447 }
01448
01449 public function addTrackbacks() {
01450 global $wgOut, $wgUser;
01451 $dbr = wfGetDB( DB_SLAVE );
01452 $tbs = $dbr->select( 'trackbacks',
01453 array( 'tb_id', 'tb_title', 'tb_url', 'tb_ex', 'tb_name' ),
01454 array( 'tb_page' => $this->getID() )
01455 );
01456 if ( !$dbr->numRows( $tbs ) ) return;
01457
01458 $wgOut->preventClickjacking();
01459
01460 $tbtext = "";
01461 while ( $o = $dbr->fetchObject( $tbs ) ) {
01462 $rmvtxt = "";
01463 if ( $wgUser->isAllowed( 'trackback' ) ) {
01464 $delurl = $this->mTitle->getFullURL( "action=deletetrackback&tbid=" .
01465 $o->tb_id . "&token=" . urlencode( $wgUser->editToken() ) );
01466 $rmvtxt = wfMsg( 'trackbackremove', htmlspecialchars( $delurl ) );
01467 }
01468 $tbtext .= "\n";
01469 $tbtext .= wfMsg( strlen( $o->tb_ex ) ? 'trackbackexcerpt' : 'trackback',
01470 $o->tb_title,
01471 $o->tb_url,
01472 $o->tb_ex,
01473 $o->tb_name,
01474 $rmvtxt );
01475 }
01476 $wgOut->wrapWikiMsg( "<div id='mw_trackbacks'>$1</div>\n", array( 'trackbackbox', $tbtext ) );
01477 $this->mTitle->invalidateCache();
01478 }
01479
01480 public function deletetrackback() {
01481 global $wgUser, $wgRequest, $wgOut;
01482 if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) {
01483 $wgOut->addWikiMsg( 'sessionfailure' );
01484 return;
01485 }
01486
01487 $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
01488 if ( count( $permission_errors ) ) {
01489 $wgOut->showPermissionsErrorPage( $permission_errors );
01490 return;
01491 }
01492
01493 $db = wfGetDB( DB_MASTER );
01494 $db->delete( 'trackbacks', array( 'tb_id' => $wgRequest->getInt( 'tbid' ) ) );
01495
01496 $wgOut->addWikiMsg( 'trackbackdeleteok' );
01497 $this->mTitle->invalidateCache();
01498 }
01499
01500 public function render() {
01501 global $wgOut;
01502 $wgOut->setArticleBodyOnly( true );
01503 $this->view();
01504 }
01505
01509 public function purge() {
01510 global $wgUser, $wgRequest, $wgOut;
01511 if ( $wgUser->isAllowed( 'purge' ) || $wgRequest->wasPosted() ) {
01512 if ( wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
01513 $this->doPurge();
01514 $this->view();
01515 }
01516 } else {
01517 $action = htmlspecialchars( $wgRequest->getRequestURL() );
01518 $button = wfMsgExt( 'confirm_purge_button', array( 'escapenoentities' ) );
01519 $form = "<form method=\"post\" action=\"$action\">\n" .
01520 "<input type=\"submit\" name=\"submit\" value=\"$button\" />\n" .
01521 "</form>\n";
01522 $top = wfMsgExt( 'confirm-purge-top', array( 'parse' ) );
01523 $bottom = wfMsgExt( 'confirm-purge-bottom', array( 'parse' ) );
01524 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
01525 $wgOut->setRobotPolicy( 'noindex,nofollow' );
01526 $wgOut->addHTML( $top . $form . $bottom );
01527 }
01528 }
01529
01533 public function doPurge() {
01534 global $wgUseSquid;
01535
01536 $this->mTitle->invalidateCache();
01537
01538 if ( $wgUseSquid ) {
01539
01540 $dbw = wfGetDB( DB_MASTER );
01541 $dbw->commit();
01542
01543
01544 $update = SquidUpdate::newSimplePurge( $this->mTitle );
01545 $update->doUpdate();
01546 }
01547 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
01548 global $wgMessageCache;
01549 if ( $this->getID() == 0 ) {
01550 $text = false;
01551 } else {
01552 $text = $this->getRawText();
01553 }
01554 $wgMessageCache->replace( $this->mTitle->getDBkey(), $text );
01555 }
01556 }
01557
01569 public function insertOn( $dbw ) {
01570 wfProfileIn( __METHOD__ );
01571
01572 $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
01573 $dbw->insert( 'page', array(
01574 'page_id' => $page_id,
01575 'page_namespace' => $this->mTitle->getNamespace(),
01576 'page_title' => $this->mTitle->getDBkey(),
01577 'page_counter' => 0,
01578 'page_restrictions' => '',
01579 'page_is_redirect' => 0, # Will set this shortly...
01580 'page_is_new' => 1,
01581 'page_random' => wfRandom(),
01582 'page_touched' => $dbw->timestamp(),
01583 'page_latest' => 0, # Fill this in shortly...
01584 'page_len' => 0, # Fill this in shortly...
01585 ), __METHOD__, 'IGNORE' );
01586
01587 $affected = $dbw->affectedRows();
01588 if ( $affected ) {
01589 $newid = $dbw->insertId();
01590 $this->mTitle->resetArticleId( $newid );
01591 }
01592 wfProfileOut( __METHOD__ );
01593 return $affected ? $newid : false;
01594 }
01595
01611 public function updateRevisionOn( &$dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
01612 wfProfileIn( __METHOD__ );
01613
01614 $text = $revision->getText();
01615 $rt = Title::newFromRedirect( $text );
01616
01617 $conditions = array( 'page_id' => $this->getId() );
01618 if ( !is_null( $lastRevision ) ) {
01619 # An extra check against threads stepping on each other
01620 $conditions['page_latest'] = $lastRevision;
01621 }
01622
01623 $dbw->update( 'page',
01624 array(
01625 'page_latest' => $revision->getId(),
01626 'page_touched' => $dbw->timestamp(),
01627 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
01628 'page_is_redirect' => $rt !== null ? 1 : 0,
01629 'page_len' => strlen( $text ),
01630 ),
01631 $conditions,
01632 __METHOD__ );
01633
01634 $result = $dbw->affectedRows() != 0;
01635 if ( $result ) {
01636 $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
01637 }
01638
01639 wfProfileOut( __METHOD__ );
01640 return $result;
01641 }
01642
01654 public function updateRedirectOn( &$dbw, $redirectTitle, $lastRevIsRedirect = null ) {
01655
01656
01657
01658 $isRedirect = !is_null( $redirectTitle );
01659 if ( $isRedirect || is_null( $lastRevIsRedirect ) || $lastRevIsRedirect !== $isRedirect ) {
01660 wfProfileIn( __METHOD__ );
01661 if ( $isRedirect ) {
01662
01663 $set = array(
01664 'rd_namespace' => $redirectTitle->getNamespace(),
01665 'rd_title' => $redirectTitle->getDBkey(),
01666 'rd_from' => $this->getId(),
01667 );
01668 $dbw->replace( 'redirect', array( 'rd_from' ), $set, __METHOD__ );
01669 } else {
01670
01671 $where = array( 'rd_from' => $this->getId() );
01672 $dbw->delete( 'redirect', $where, __METHOD__ );
01673 }
01674 if ( $this->getTitle()->getNamespace() == NS_FILE ) {
01675 RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
01676 }
01677 wfProfileOut( __METHOD__ );
01678 return ( $dbw->affectedRows() != 0 );
01679 }
01680 return true;
01681 }
01682
01690 public function updateIfNewerOn( &$dbw, $revision ) {
01691 wfProfileIn( __METHOD__ );
01692 $row = $dbw->selectRow(
01693 array( 'revision', 'page' ),
01694 array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
01695 array(
01696 'page_id' => $this->getId(),
01697 'page_latest=rev_id' ),
01698 __METHOD__ );
01699 if ( $row ) {
01700 if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
01701 wfProfileOut( __METHOD__ );
01702 return false;
01703 }
01704 $prev = $row->rev_id;
01705 $lastRevIsRedirect = (bool)$row->page_is_redirect;
01706 } else {
01707 # No or missing previous revision; mark the page as new
01708 $prev = 0;
01709 $lastRevIsRedirect = null;
01710 }
01711 $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
01712 wfProfileOut( __METHOD__ );
01713 return $ret;
01714 }
01715
01720 public function replaceSection( $section, $text, $summary = '', $edittime = null ) {
01721 wfProfileIn( __METHOD__ );
01722 if ( strval( $section ) == '' ) {
01723
01724 } else {
01725 if ( is_null( $edittime ) ) {
01726 $rev = Revision::newFromTitle( $this->mTitle );
01727 } else {
01728 $dbw = wfGetDB( DB_MASTER );
01729 $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
01730 }
01731 if ( !$rev ) {
01732 wfDebug( "Article::replaceSection asked for bogus section (page: " .
01733 $this->getId() . "; section: $section; edittime: $edittime)\n" );
01734 return null;
01735 }
01736 $oldtext = $rev->getText();
01737
01738 if ( $section == 'new' ) {
01739 # Inserting a new section
01740 $subject = $summary ? wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" : '';
01741 $text = strlen( trim( $oldtext ) ) > 0
01742 ? "{$oldtext}\n\n{$subject}{$text}"
01743 : "{$subject}{$text}";
01744 } else {
01745 # Replacing an existing section; roll out the big guns
01746 global $wgParser;
01747 $text = $wgParser->replaceSection( $oldtext, $section, $text );
01748 }
01749 }
01750 wfProfileOut( __METHOD__ );
01751 return $text;
01752 }
01753
01758 function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC = false, $comment = false, $bot = false ) {
01759 $flags = EDIT_NEW | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
01760 ( $isminor ? EDIT_MINOR : 0 ) |
01761 ( $suppressRC ? EDIT_SUPPRESS_RC : 0 ) |
01762 ( $bot ? EDIT_FORCE_BOT : 0 );
01763
01764 # If this is a comment, add the summary as headline
01765 if ( $comment && $summary != "" ) {
01766 $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" . $text;
01767 }
01768
01769 $this->doEdit( $text, $summary, $flags );
01770
01771 $dbw = wfGetDB( DB_MASTER );
01772 if ( $watchthis ) {
01773 if ( !$this->mTitle->userIsWatching() ) {
01774 $dbw->begin();
01775 $this->doWatch();
01776 $dbw->commit();
01777 }
01778 } else {
01779 if ( $this->mTitle->userIsWatching() ) {
01780 $dbw->begin();
01781 $this->doUnwatch();
01782 $dbw->commit();
01783 }
01784 }
01785 $this->doRedirect( $this->isRedirect( $text ) );
01786 }
01787
01791 function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) {
01792 $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
01793 ( $minor ? EDIT_MINOR : 0 ) |
01794 ( $forceBot ? EDIT_FORCE_BOT : 0 );
01795
01796 $status = $this->doEdit( $text, $summary, $flags );
01797 if ( !$status->isOK() ) {
01798 return false;
01799 }
01800
01801 $dbw = wfGetDB( DB_MASTER );
01802 if ( $watchthis ) {
01803 if ( !$this->mTitle->userIsWatching() ) {
01804 $dbw->begin();
01805 $this->doWatch();
01806 $dbw->commit();
01807 }
01808 } else {
01809 if ( $this->mTitle->userIsWatching() ) {
01810 $dbw->begin();
01811 $this->doUnwatch();
01812 $dbw->commit();
01813 }
01814 }
01815
01816 $extraQuery = '';
01817 wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraQuery ) );
01818
01819 $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraQuery );
01820 return true;
01821 }
01822
01873 public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
01874 global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
01875
01876 # Low-level sanity check
01877 if ( $this->mTitle->getText() == '' ) {
01878 throw new MWException( 'Something is trying to edit an article with an empty title' );
01879 }
01880
01881 wfProfileIn( __METHOD__ );
01882
01883 $user = is_null( $user ) ? $wgUser : $user;
01884 $status = Status::newGood( array() );
01885
01886 # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
01887 $this->loadPageData();
01888
01889 if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
01890 $aid = $this->mTitle->getArticleID();
01891 if ( $aid ) {
01892 $flags |= EDIT_UPDATE;
01893 } else {
01894 $flags |= EDIT_NEW;
01895 }
01896 }
01897
01898 if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
01899 $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
01900 {
01901 wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
01902 wfProfileOut( __METHOD__ );
01903 if ( $status->isOK() ) {
01904 $status->fatal( 'edit-hook-aborted' );
01905 }
01906 return $status;
01907 }
01908
01909 # Silently ignore EDIT_MINOR if not allowed
01910 $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
01911 $bot = $flags & EDIT_FORCE_BOT;
01912
01913 $oldtext = $this->getRawText();
01914 $oldsize = strlen( $oldtext );
01915
01916 # Provide autosummaries if one is not provided and autosummaries are enabled.
01917 if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
01918 $summary = $this->getAutosummary( $oldtext, $text, $flags );
01919 }
01920
01921 $editInfo = $this->prepareTextForEdit( $text );
01922 $text = $editInfo->pst;
01923 $newsize = strlen( $text );
01924
01925 $dbw = wfGetDB( DB_MASTER );
01926 $now = wfTimestampNow();
01927 $this->mTimestamp = $now;
01928
01929 if ( $flags & EDIT_UPDATE ) {
01930 # Update article, but only if changed.
01931 $status->value['new'] = false;
01932 # Make sure the revision is either completely inserted or not inserted at all
01933 if ( !$wgDBtransactions ) {
01934 $userAbort = ignore_user_abort( true );
01935 }
01936
01937 $revisionId = 0;
01938
01939 $changed = ( strcmp( $text, $oldtext ) != 0 );
01940
01941 if ( $changed ) {
01942 $this->mGoodAdjustment = (int)$this->isCountable( $text )
01943 - (int)$this->isCountable( $oldtext );
01944 $this->mTotalAdjustment = 0;
01945
01946 if ( !$this->mLatest ) {
01947 # Article gone missing
01948 wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
01949 $status->fatal( 'edit-gone-missing' );
01950 wfProfileOut( __METHOD__ );
01951 return $status;
01952 }
01953
01954 $revision = new Revision( array(
01955 'page' => $this->getId(),
01956 'comment' => $summary,
01957 'minor_edit' => $isminor,
01958 'text' => $text,
01959 'parent_id' => $this->mLatest,
01960 'user' => $user->getId(),
01961 'user_text' => $user->getName(),
01962 ) );
01963
01964 $dbw->begin();
01965 $revisionId = $revision->insertOn( $dbw );
01966
01967 # Update page
01968 #
01969 # Note that we use $this->mLatest instead of fetching a value from the master DB
01970 # during the course of this function. This makes sure that EditPage can detect
01971 # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
01972 # before this function is called. A previous function used a separate query, this
01973 # creates a window where concurrent edits can cause an ignored edit conflict.
01974 $ok = $this->updateRevisionOn( $dbw, $revision, $this->mLatest );
01975
01976 if ( !$ok ) {
01977
01978 $status->fatal( 'edit-conflict' );
01979 # Delete the invalid revision if the DB is not transactional
01980 if ( !$wgDBtransactions ) {
01981 $dbw->delete( 'revision', array( 'rev_id' => $revisionId ), __METHOD__ );
01982 }
01983 $revisionId = 0;
01984 $dbw->rollback();
01985 } else {
01986 global $wgUseRCPatrol;
01987 wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
01988 # Update recentchanges
01989 if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
01990 # Mark as patrolled if the user can do so
01991 $patrolled = $wgUseRCPatrol && $this->mTitle->userCan( 'autopatrol' );
01992 # Add RC row to the DB
01993 $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
01994 $this->mLatest, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
01995 $revisionId, $patrolled
01996 );
01997 # Log auto-patrolled edits
01998 if ( $patrolled ) {
01999 PatrolLog::record( $rc, true );
02000 }
02001 }
02002 $user->incEditCount();
02003 $dbw->commit();
02004 }
02005 } else {
02006 $status->warning( 'edit-no-change' );
02007 $revision = null;
02008
02009 $revisionId = $this->getRevIdFetched();
02010
02011
02012 $this->mTitle->invalidateCache();
02013 }
02014
02015 if ( !$wgDBtransactions ) {
02016 ignore_user_abort( $userAbort );
02017 }
02018
02019 if ( !$status->isOK() ) {
02020 wfProfileOut( __METHOD__ );
02021 return $status;
02022 }
02023
02024 # Invalidate cache of this article and all pages using this article
02025 # as a template. Partly deferred.
02026 Article::onArticleEdit( $this->mTitle );
02027 # Update links tables, site stats, etc.
02028 $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
02029 } else {
02030 # Create new article
02031 $status->value['new'] = true;
02032
02033 # Set statistics members
02034 # We work out if it's countable after PST to avoid counter drift
02035 # when articles are created with {{subst:}}
02036 $this->mGoodAdjustment = (int)$this->isCountable( $text );
02037 $this->mTotalAdjustment = 1;
02038
02039 $dbw->begin();
02040
02041 # Add the page record; stake our claim on this title!
02042 # This will return false if the article already exists
02043 $newid = $this->insertOn( $dbw );
02044
02045 if ( $newid === false ) {
02046 $dbw->rollback();
02047 $status->fatal( 'edit-already-exists' );
02048 wfProfileOut( __METHOD__ );
02049 return $status;
02050 }
02051
02052 # Save the revision text...
02053 $revision = new Revision( array(
02054 'page' => $newid,
02055 'comment' => $summary,
02056 'minor_edit' => $isminor,
02057 'text' => $text,
02058 'user' => $user->getId(),
02059 'user_text' => $user->getName(),
02060 ) );
02061 $revisionId = $revision->insertOn( $dbw );
02062
02063 $this->mTitle->resetArticleID( $newid );
02064
02065 # Update the page record with revision data
02066 $this->updateRevisionOn( $dbw, $revision, 0 );
02067
02068 wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
02069 # Update recentchanges
02070 if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
02071 global $wgUseRCPatrol, $wgUseNPPatrol;
02072 # Mark as patrolled if the user can do so
02073 $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && $this->mTitle->userCan( 'autopatrol' );
02074 # Add RC row to the DB
02075 $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
02076 '', strlen( $text ), $revisionId, $patrolled );
02077 # Log auto-patrolled edits
02078 if ( $patrolled ) {
02079 PatrolLog::record( $rc, true );
02080 }
02081 }
02082 $user->incEditCount();
02083 $dbw->commit();
02084
02085 # Update links, etc.
02086 $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, true );
02087
02088 # Clear caches
02089 Article::onArticleCreate( $this->mTitle );
02090
02091 wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
02092 $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
02093 }
02094
02095 # Do updates right now unless deferral was requested
02096 if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
02097 wfDoUpdates();
02098 }
02099
02100
02101 $status->value['revision'] = $revision;
02102
02103 wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
02104 $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
02105
02106 wfProfileOut( __METHOD__ );
02107 return $status;
02108 }
02109
02113 public function showArticle( $text, $subtitle , $sectionanchor = '', $me2, $now, $summary, $oldid ) {
02114 wfDeprecated( __METHOD__ );
02115 $this->doRedirect( $this->isRedirect( $text ), $sectionanchor );
02116 }
02117
02126 public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
02127 global $wgOut;
02128 if ( $noRedir ) {
02129 $query = 'redirect=no';
02130 if ( $extraQuery )
02131 $query .= "&$query";
02132 } else {
02133 $query = $extraQuery;
02134 }
02135 $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor );
02136 }
02137
02141 public function markpatrolled() {
02142 global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUseNPPatrol, $wgUser;
02143 $wgOut->setRobotPolicy( 'noindex,nofollow' );
02144
02145 # If we haven't been given an rc_id value, we can't do anything
02146 $rcid = (int) $wgRequest->getVal( 'rcid' );
02147 $rc = RecentChange::newFromId( $rcid );
02148 if ( is_null( $rc ) ) {
02149 $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
02150 return;
02151 }
02152
02153 # It would be nice to see where the user had actually come from, but for now just guess
02154 $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges';
02155 $return = SpecialPage::getTitleFor( $returnto );
02156
02157 $dbw = wfGetDB( DB_MASTER );
02158 $errors = $rc->doMarkPatrolled();
02159
02160 if ( in_array( array( 'rcpatroldisabled' ), $errors ) ) {
02161 $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
02162 return;
02163 }
02164
02165 if ( in_array( array( 'hookaborted' ), $errors ) ) {
02166
02167 return;
02168 }
02169
02170 if ( in_array( array( 'markedaspatrollederror-noautopatrol' ), $errors ) ) {
02171 $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) );
02172 $wgOut->addWikiMsg( 'markedaspatrollederror-noautopatrol' );
02173 $wgOut->returnToMain( false, $return );
02174 return;
02175 }
02176
02177 if ( !empty( $errors ) ) {
02178 $wgOut->showPermissionsErrorPage( $errors );
02179 return;
02180 }
02181
02182 # Inform the user
02183 $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) );
02184 $wgOut->addWikiMsg( 'markedaspatrolledtext', $rc->getTitle()->getPrefixedText() );
02185 $wgOut->returnToMain( false, $return );
02186 }
02187
02191 public function watch() {
02192 global $wgUser, $wgOut;
02193 if ( $wgUser->isAnon() ) {
02194 $wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
02195 return;
02196 }
02197 if ( wfReadOnly() ) {
02198 $wgOut->readOnlyPage();
02199 return;
02200 }
02201 if ( $this->doWatch() ) {
02202 $wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
02203 $wgOut->setRobotPolicy( 'noindex,nofollow' );
02204 $wgOut->addWikiMsg( 'addedwatchtext', $this->mTitle->getPrefixedText() );
02205 }
02206 $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
02207 }
02208
02213 public function doWatch() {
02214 global $wgUser;
02215 if ( $wgUser->isAnon() ) {
02216 return false;
02217 }
02218 if ( wfRunHooks( 'WatchArticle', array( &$wgUser, &$this ) ) ) {
02219 $wgUser->addWatch( $this->mTitle );
02220 return wfRunHooks( 'WatchArticleComplete', array( &$wgUser, &$this ) );
02221 }
02222 return false;
02223 }
02224
02228 public function unwatch() {
02229 global $wgUser, $wgOut;
02230 if ( $wgUser->isAnon() ) {
02231 $wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
02232 return;
02233 }
02234 if ( wfReadOnly() ) {
02235 $wgOut->readOnlyPage();
02236 return;
02237 }
02238 if ( $this->doUnwatch() ) {
02239 $wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
02240 $wgOut->setRobotPolicy( 'noindex,nofollow' );
02241 $wgOut->addWikiMsg( 'removedwatchtext', $this->mTitle->getPrefixedText() );
02242 }
02243 $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
02244 }
02245
02250 public function doUnwatch() {
02251 global $wgUser;
02252 if ( $wgUser->isAnon() ) {
02253 return false;
02254 }
02255 if ( wfRunHooks( 'UnwatchArticle', array( &$wgUser, &$this ) ) ) {
02256 $wgUser->removeWatch( $this->mTitle );
02257 return wfRunHooks( 'UnwatchArticleComplete', array( &$wgUser, &$this ) );
02258 }
02259 return false;
02260 }
02261
02265 public function protect() {
02266 $form = new ProtectionForm( $this );
02267 $form->execute();
02268 }
02269
02273 public function unprotect() {
02274 $this->protect();
02275 }
02276
02286 public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
02287 global $wgUser, $wgContLang;
02288
02289 $restrictionTypes = $this->mTitle->getRestrictionTypes();
02290
02291 $id = $this->mTitle->getArticleID();
02292 if ( $id <= 0 ) {
02293 wfDebug( "updateRestrictions failed: $id <= 0\n" );
02294 return false;
02295 }
02296
02297 if ( wfReadOnly() ) {
02298 wfDebug( "updateRestrictions failed: read-only\n" );
02299 return false;
02300 }
02301
02302 if ( !$this->mTitle->userCan( 'protect' ) ) {
02303 wfDebug( "updateRestrictions failed: insufficient permissions\n" );
02304 return false;
02305 }
02306
02307 if ( !$cascade ) {
02308 $cascade = false;
02309 }
02310
02311
02312 Title::purgeExpiredRestrictions();
02313
02314 # FIXME: Same limitations as described in ProtectionForm.php (line 37);
02315 # we expect a single selection, but the schema allows otherwise.
02316 $current = array();
02317 $updated = Article::flattenRestrictions( $limit );
02318 $changed = false;
02319 foreach ( $restrictionTypes as $action ) {
02320 if ( isset( $expiry[$action] ) ) {
02321 # Get current restrictions on $action
02322 $aLimits = $this->mTitle->getRestrictions( $action );
02323 $current[$action] = implode( '', $aLimits );
02324 # Are any actual restrictions being dealt with here?
02325 $aRChanged = count( $aLimits ) || !empty( $limit[$action] );
02326 # If something changed, we need to log it. Checking $aRChanged
02327 # assures that "unprotecting" a page that is not protected does
02328 # not log just because the expiry was "changed".
02329 if ( $aRChanged && $this->mTitle->mRestrictionsExpiry[$action] != $expiry[$action] ) {
02330 $changed = true;
02331 }
02332 }
02333 }
02334
02335 $current = Article::flattenRestrictions( $current );
02336
02337 $changed = ( $changed || $current != $updated );
02338 $changed = $changed || ( $updated && $this->mTitle->areRestrictionsCascading() != $cascade );
02339 $protect = ( $updated != '' );
02340
02341 # If nothing's changed, do nothing
02342 if ( $changed ) {
02343 if ( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) {
02344
02345 $dbw = wfGetDB( DB_MASTER );
02346
02347 # Prepare a null revision to be added to the history
02348 $modified = $current != '' && $protect;
02349 if ( $protect ) {
02350 $comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle';
02351 } else {
02352 $comment_type = 'unprotectedarticle';
02353 }
02354 $comment = $wgContLang->ucfirst( wfMsgForContent( $comment_type, $this->mTitle->getPrefixedText() ) );
02355
02356 # Only restrictions with the 'protect' right can cascade...
02357 # Otherwise, people who cannot normally protect can "protect" pages via transclusion
02358 $editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
02359 # The schema allows multiple restrictions
02360 if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) )
02361 $cascade = false;
02362 $cascade_description = '';
02363 if ( $cascade ) {
02364 $cascade_description = ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
02365 }
02366
02367 if ( $reason )
02368 $comment .= ": $reason";
02369
02370 $editComment = $comment;
02371 $encodedExpiry = array();
02372 $protect_description = '';
02373 foreach ( $limit as $action => $restrictions ) {
02374 if ( !isset( $expiry[$action] ) )
02375 $expiry[$action] = 'infinite';
02376
02377 $encodedExpiry[$action] = Block::encodeExpiry( $expiry[$action], $dbw );
02378 if ( $restrictions != '' ) {
02379 $protect_description .= "[$action=$restrictions] (";
02380 if ( $encodedExpiry[$action] != 'infinity' ) {
02381 $protect_description .= wfMsgForContent( 'protect-expiring',
02382 $wgContLang->timeanddate( $expiry[$action], false, false ) ,
02383 $wgContLang->date( $expiry[$action], false, false ) ,
02384 $wgContLang->time( $expiry[$action], false, false ) );
02385 } else {
02386 $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' );
02387 }
02388 $protect_description .= ') ';
02389 }
02390 }
02391 $protect_description = trim( $protect_description );
02392
02393 if ( $protect_description && $protect )
02394 $editComment .= " ($protect_description)";
02395 if ( $cascade )
02396 $editComment .= "$cascade_description";
02397 # Update restrictions table
02398 foreach ( $limit as $action => $restrictions ) {
02399 if ( $restrictions != '' ) {
02400 $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
02401 array( 'pr_page' => $id,
02402 'pr_type' => $action,
02403 'pr_level' => $restrictions,
02404 'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
02405 'pr_expiry' => $encodedExpiry[$action] ), __METHOD__ );
02406 } else {
02407 $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
02408 'pr_type' => $action ), __METHOD__ );
02409 }
02410 }
02411
02412 # Insert a null revision
02413 $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
02414 $nullRevId = $nullRevision->insertOn( $dbw );
02415
02416 $latest = $this->getLatest();
02417 # Update page record
02418 $dbw->update( 'page',
02419 array(
02420 'page_touched' => $dbw->timestamp(),
02421 'page_restrictions' => '',
02422 'page_latest' => $nullRevId
02423 ), array(
02424 'page_id' => $id
02425 ), 'Article::protect'
02426 );
02427
02428 wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $wgUser ) );
02429 wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) );
02430
02431 # Update the protection log
02432 $log = new LogPage( 'protect' );
02433 if ( $protect ) {
02434 $params = array( $protect_description, $cascade ? 'cascade' : '' );
02435 $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason ), $params );
02436 } else {
02437 $log->addEntry( 'unprotect', $this->mTitle, $reason );
02438 }
02439
02440 } # End hook
02441 } # End "changed" check
02442
02443 return true;
02444 }
02445
02452 protected static function flattenRestrictions( $limit ) {
02453 if ( !is_array( $limit ) ) {
02454 throw new MWException( 'Article::flattenRestrictions given non-array restriction set' );
02455 }
02456 $bits = array();
02457 ksort( $limit );
02458 foreach ( $limit as $action => $restrictions ) {
02459 if ( $restrictions != '' ) {
02460 $bits[] = "$action=$restrictions";
02461 }
02462 }
02463 return implode( ':', $bits );
02464 }
02465
02470 public function generateReason( &$hasHistory ) {
02471 global $wgContLang;
02472 $dbw = wfGetDB( DB_MASTER );
02473
02474 $rev = Revision::newFromTitle( $this->mTitle );
02475 if ( is_null( $rev ) )
02476 return false;
02477
02478
02479 $contents = $rev->getText();
02480 $blank = false;
02481
02482
02483 if ( $contents == '' ) {
02484 $prev = $rev->getPrevious();
02485 if ( $prev ) {
02486 $contents = $prev->getText();
02487 $blank = true;
02488 }
02489 }
02490
02491
02492
02493 $res = $dbw->select( 'revision', 'rev_user_text',
02494 array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
02495 __METHOD__,
02496 array( 'LIMIT' => 20 )
02497 );
02498 if ( $res === false )
02499
02500 return false;
02501
02502 $hasHistory = ( $res->numRows() > 1 );
02503 $row = $dbw->fetchObject( $res );
02504 $onlyAuthor = $row->rev_user_text;
02505
02506 foreach ( $res as $row ) {
02507 if ( $row->rev_user_text != $onlyAuthor ) {
02508 $onlyAuthor = false;
02509 break;
02510 }
02511 }
02512 $dbw->freeResult( $res );
02513
02514
02515 if ( $blank ) {
02516
02517
02518 $reason = wfMsgForContent( 'exbeforeblank', '$1' );
02519 } else {
02520 if ( $onlyAuthor )
02521 $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
02522 else
02523 $reason = wfMsgForContent( 'excontent', '$1' );
02524 }
02525
02526 if ( $reason == '-' ) {
02527
02528 return '';
02529 }
02530
02531
02532 $contents = preg_replace( "/[\n\r]/", ' ', $contents );
02533
02534
02535 $maxLength = 255 - ( strlen( $reason ) - 2 ) - 3;
02536 $contents = $wgContLang->truncate( $contents, $maxLength );
02537
02538 $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
02539
02540 $reason = str_replace( '$1', $contents, $reason );
02541 return $reason;
02542 }
02543
02544
02545
02546
02547
02548 public function delete() {
02549 global $wgUser, $wgOut, $wgRequest;
02550
02551 $confirm = $wgRequest->wasPosted() &&
02552 $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
02553
02554 $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
02555 $this->DeleteReason = $wgRequest->getText( 'wpReason' );
02556
02557 $reason = $this->DeleteReasonList;
02558
02559 if ( $reason != 'other' && $this->DeleteReason != '' ) {
02560
02561 $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason;
02562 } elseif ( $reason == 'other' ) {
02563 $reason = $this->DeleteReason;
02564 }
02565 # Flag to hide all contents of the archived revisions
02566 $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
02567
02568 # This code desperately needs to be totally rewritten
02569
02570 # Read-only check...
02571 if ( wfReadOnly() ) {
02572 $wgOut->readOnlyPage();
02573 return;
02574 }
02575
02576 # Check permissions
02577 $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
02578
02579 if ( count( $permission_errors ) > 0 ) {
02580 $wgOut->showPermissionsErrorPage( $permission_errors );
02581 return;
02582 }
02583
02584 $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->mTitle->getPrefixedText() ) );
02585
02586 # Better double-check that it hasn't been deleted yet!
02587 $dbw = wfGetDB( DB_MASTER );
02588 $conds = $this->mTitle->pageCond();
02589 $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
02590 if ( $latest === false ) {
02591 $wgOut->showFatalError(
02592 Html::rawElement(
02593 'div',
02594 array( 'class' => 'error mw-error-cannotdelete' ),
02595 wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
02596 )
02597 );
02598 $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
02599 LogEventsList::showLogExtract(
02600 $wgOut,
02601 'delete',
02602 $this->mTitle->getPrefixedText()
02603 );
02604 return;
02605 }
02606
02607 # Hack for big sites
02608 $bigHistory = $this->isBigDeletion();
02609 if ( $bigHistory && !$this->mTitle->userCan( 'bigdelete' ) ) {
02610 global $wgLang, $wgDeleteRevisionsLimit;
02611 $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n",
02612 array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
02613 return;
02614 }
02615
02616 if ( $confirm ) {
02617 $this->doDelete( $reason, $suppress );
02618 if ( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
02619 $this->doWatch();
02620 } elseif ( $this->mTitle->userIsWatching() ) {
02621 $this->doUnwatch();
02622 }
02623 return;
02624 }
02625
02626
02627 $hasHistory = false;
02628 if ( !$reason ) $reason = $this->generateReason( $hasHistory );
02629
02630
02631 if ( $hasHistory && !$confirm ) {
02632 global $wgLang;
02633 $skin = $wgUser->getSkin();
02634 $revisions = $this->estimateRevisionCount();
02635 $wgOut->addHTML( '<strong class="mw-delete-warning-revisions">' .
02636 wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) .
02637 wfMsgHtml( 'word-separator' ) . $skin->historyLink() .
02638 '</strong>'
02639 );
02640 if ( $bigHistory ) {
02641 global $wgDeleteRevisionsLimit;
02642 $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n",
02643 array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
02644 }
02645 }
02646
02647 return $this->confirmDelete( $reason );
02648 }
02649
02653 public function isBigDeletion() {
02654 global $wgDeleteRevisionsLimit;
02655 if ( $wgDeleteRevisionsLimit ) {
02656 $revCount = $this->estimateRevisionCount();
02657 return $revCount > $wgDeleteRevisionsLimit;
02658 }
02659 return false;
02660 }
02661
02665 public function estimateRevisionCount() {
02666 $dbr = wfGetDB( DB_SLAVE );
02667
02668
02669
02670 return $dbr->estimateRowCount( 'revision', '*',
02671 array( 'rev_page' => $this->getId() ), __METHOD__ );
02672 }
02673
02680 public function getLastNAuthors( $num, $revLatest = 0 ) {
02681 wfProfileIn( __METHOD__ );
02682
02683
02684 $continue = 2;
02685 $db = wfGetDB( DB_SLAVE );
02686 do {
02687 $res = $db->select( array( 'page', 'revision' ),
02688 array( 'rev_id', 'rev_user_text' ),
02689 array(
02690 'page_namespace' => $this->mTitle->getNamespace(),
02691 'page_title' => $this->mTitle->getDBkey(),
02692 'rev_page = page_id'
02693 ), __METHOD__, $this->getSelectOptions( array(
02694 'ORDER BY' => 'rev_timestamp DESC',
02695 'LIMIT' => $num
02696 ) )
02697 );
02698 if ( !$res ) {
02699 wfProfileOut( __METHOD__ );
02700 return array();
02701 }
02702 $row = $db->fetchObject( $res );
02703 if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
02704 $db = wfGetDB( DB_MASTER );
02705 $continue--;
02706 } else {
02707 $continue = 0;
02708 }
02709 } while ( $continue );
02710
02711 $authors = array( $row->rev_user_text );
02712 while ( $row = $db->fetchObject( $res ) ) {
02713 $authors[] = $row->rev_user_text;
02714 }
02715 wfProfileOut( __METHOD__ );
02716 return $authors;
02717 }
02718
02723 public function confirmDelete( $reason ) {
02724 global $wgOut, $wgUser;
02725
02726 wfDebug( "Article::confirmDelete\n" );
02727
02728 $deleteBackLink = $wgUser->getSkin()->link(
02729 $this->mTitle,
02730 null,
02731 array(),
02732 array(),
02733 array( 'known', 'noclasses' )
02734 );
02735 $wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) );
02736 $wgOut->setRobotPolicy( 'noindex,nofollow' );
02737 $wgOut->addWikiMsg( 'confirmdeletetext' );
02738
02739 wfRunHooks( 'ArticleConfirmDelete', array( $this, $wgOut, &$reason ) );
02740
02741 if ( $wgUser->isAllowed( 'suppressrevision' ) ) {
02742 $suppress = "<tr id=\"wpDeleteSuppressRow\" name=\"wpDeleteSuppressRow\">
02743 <td></td>
02744 <td class='mw-input'><strong>" .
02745 Xml::checkLabel( wfMsg( 'revdelete-suppress' ),
02746 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
02747 "</strong></td>
02748 </tr>";
02749 } else {
02750 $suppress = '';
02751 }
02752 $checkWatch = $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching();
02753
02754 $form = Xml::openElement( 'form', array( 'method' => 'post',
02755 'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
02756 Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
02757 Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) .
02758 Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
02759 "<tr id=\"wpDeleteReasonListRow\">
02760 <td class='mw-label'>" .
02761 Xml::label( wfMsg( 'deletecomment' ), 'wpDeleteReasonList' ) .
02762 "</td>
02763 <td class='mw-input'>" .
02764 Xml::listDropDown( 'wpDeleteReasonList',
02765 wfMsgForContent( 'deletereason-dropdown' ),
02766 wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) .
02767 "</td>
02768 </tr>
02769 <tr id=\"wpDeleteReasonRow\">
02770 <td class='mw-label'>" .
02771 Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) .
02772 "</td>
02773 <td class='mw-input'>" .
02774 Html::input( 'wpReason', $reason, 'text', array(
02775 'size' => '60',
02776 'maxlength' => '255',
02777 'tabindex' => '2',
02778 'id' => 'wpReason',
02779 'autofocus'
02780 ) ) .
02781 "</td>
02782 </tr>";
02783 # Dissalow watching is user is not logged in
02784 if ( $wgUser->isLoggedIn() ) {
02785 $form .= "
02786 <tr>
02787 <td></td>
02788 <td class='mw-input'>" .
02789 Xml::checkLabel( wfMsg( 'watchthis' ),
02790 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
02791 "</td>
02792 </tr>";
02793 }
02794 $form .= "
02795 $suppress
02796 <tr>
02797 <td></td>
02798 <td class='mw-submit'>" .
02799 Xml::submitButton( wfMsg( 'deletepage' ),
02800 array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
02801 "</td>
02802 </tr>" .
02803 Xml::closeElement( 'table' ) .
02804 Xml::closeElement( 'fieldset' ) .
02805 Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
02806 Xml::closeElement( 'form' );
02807
02808 if ( $wgUser->isAllowed( 'editinterface' ) ) {
02809 $skin = $wgUser->getSkin();
02810 $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
02811 $link = $skin->link(
02812 $title,
02813 wfMsgHtml( 'delete-edit-reasonlist' ),
02814 array(),
02815 array( 'action' => 'edit' )
02816 );
02817 $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
02818 }
02819
02820 $wgOut->addHTML( $form );
02821 $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
02822 LogEventsList::showLogExtract(
02823 $wgOut,
02824 'delete',
02825 $this->mTitle->getPrefixedText()
02826 );
02827 }
02828
02832 public function doDelete( $reason, $suppress = false ) {
02833 global $wgOut, $wgUser;
02834 $id = $this->mTitle->getArticleID( GAID_FOR_UPDATE );
02835
02836 $error = '';
02837 if ( wfRunHooks( 'ArticleDelete', array( &$this, &$wgUser, &$reason, &$error ) ) ) {
02838 if ( $this->doDeleteArticle( $reason, $suppress, $id ) ) {
02839 $deleted = $this->mTitle->getPrefixedText();
02840
02841 $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
02842 $wgOut->setRobotPolicy( 'noindex,nofollow' );
02843
02844 $loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
02845
02846 $wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink );
02847 $wgOut->returnToMain( false );
02848 wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$wgUser, $reason, $id ) );
02849 }
02850 } else {
02851 if ( $error == '' ) {
02852 $wgOut->showFatalError(
02853 Html::rawElement(
02854 'div',
02855 array( 'class' => 'error mw-error-cannotdelete' ),
02856 wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
02857 )
02858 );
02859 $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
02860 LogEventsList::showLogExtract(
02861 $wgOut,
02862 'delete',
02863 $this->mTitle->getPrefixedText()
02864 );
02865 } else {
02866 $wgOut->showFatalError( $error );
02867 }
02868 }
02869 }
02870
02876 public function doDeleteArticle( $reason, $suppress = false, $id = 0 ) {
02877 global $wgUseSquid, $wgDeferredUpdateList;
02878 global $wgUseTrackbacks;
02879
02880 wfDebug( __METHOD__ . "\n" );
02881
02882 $dbw = wfGetDB( DB_MASTER );
02883 $ns = $this->mTitle->getNamespace();
02884 $t = $this->mTitle->getDBkey();
02885 $id = $id ? $id : $this->mTitle->getArticleID( GAID_FOR_UPDATE );
02886
02887 if ( $t == '' || $id == 0 ) {
02888 return false;
02889 }
02890
02891 $u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable( $this->getRawText() ), -1 );
02892 array_push( $wgDeferredUpdateList, $u );
02893
02894
02895 if ( $suppress ) {
02896 $bitfield = 0;
02897
02898 $bitfield |= Revision::DELETED_TEXT;
02899 $bitfield |= Revision::DELETED_COMMENT;
02900 $bitfield |= Revision::DELETED_USER;
02901 $bitfield |= Revision::DELETED_RESTRICTED;
02902 } else {
02903 $bitfield = 'rev_deleted';
02904 }
02905
02906 $dbw->begin();
02907
02908
02909
02910
02911
02912
02913
02914
02915
02916
02917 $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
02918 array(
02919 'ar_namespace' => 'page_namespace',
02920 'ar_title' => 'page_title',
02921 'ar_comment' => 'rev_comment',
02922 'ar_user' => 'rev_user',
02923 'ar_user_text' => 'rev_user_text',
02924 'ar_timestamp' => 'rev_timestamp',
02925 'ar_minor_edit' => 'rev_minor_edit',
02926 'ar_rev_id' => 'rev_id',
02927 'ar_text_id' => 'rev_text_id',
02928 'ar_text' => '\'\'',
02929 'ar_flags' => '\'\'',
02930 'ar_len' => 'rev_len',
02931 'ar_page_id' => 'page_id',
02932 'ar_deleted' => $bitfield
02933 ), array(
02934 'page_id' => $id,
02935 'page_id = rev_page'
02936 ), __METHOD__
02937 );
02938
02939 # Delete restrictions for it
02940 $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
02941
02942 # Now that it's safely backed up, delete it
02943 $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
02944 $ok = ( $dbw->affectedRows() > 0 );
02945 if ( !$ok ) {
02946 $dbw->rollback();
02947 return false;
02948 }
02949
02950 # Fix category table counts
02951 $cats = array();
02952 $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
02953 foreach ( $res as $row ) {
02954 $cats [] = $row->cl_to;
02955 }
02956 $this->updateCategoryCounts( array(), $cats );
02957
02958 # If using cascading deletes, we can skip some explicit deletes
02959 if ( !$dbw->cascadingDeletes() ) {
02960 $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
02961
02962 if ( $wgUseTrackbacks )
02963 $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
02964
02965 # Delete outgoing links
02966 $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
02967 $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
02968 $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
02969 $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
02970 $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
02971 $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
02972 $dbw->delete( 'redirect', array( 'rd_from' => $id ) );
02973 }
02974
02975 # If using cleanup triggers, we can skip some manual deletes
02976 if ( !$dbw->cleanupTriggers() ) {
02977 # Clean up recentchanges entries...
02978 $dbw->delete( 'recentchanges',
02979 array( 'rc_type != ' . RC_LOG,
02980 'rc_namespace' => $this->mTitle->getNamespace(),
02981 'rc_title' => $this->mTitle->getDBkey() ),
02982 __METHOD__ );
02983 $dbw->delete( 'recentchanges',
02984 array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
02985 __METHOD__ );
02986 }
02987
02988 # Clear caches
02989 Article::onArticleDelete( $this->mTitle );
02990
02991 # Clear the cached article id so the interface doesn't act like we exist
02992 $this->mTitle->resetArticleID( 0 );
02993
02994 # Log the deletion, if the page was suppressed, log it at Oversight instead
02995 $logtype = $suppress ? 'suppress' : 'delete';
02996 $log = new LogPage( $logtype );
02997
02998 # Make sure logging got through
02999 $log->addEntry( 'delete', $this->mTitle, $reason, array() );
03000
03001 $dbw->commit();
03002
03003 return true;
03004 }
03005
03027 public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
03028 global $wgUser;
03029 $resultDetails = null;
03030
03031 # Check permissions
03032 $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
03033 $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $wgUser );
03034 $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
03035
03036 if ( !$wgUser->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) )
03037 $errors[] = array( 'sessionfailure' );
03038
03039 if ( $wgUser->pingLimiter( 'rollback' ) || $wgUser->pingLimiter() ) {
03040 $errors[] = array( 'actionthrottledtext' );
03041 }
03042 # If there were errors, bail out now
03043 if ( !empty( $errors ) )
03044 return $errors;
03045
03046 return $this->commitRollback( $fromP, $summary, $bot, $resultDetails );
03047 }
03048
03058 public function commitRollback( $fromP, $summary, $bot, &$resultDetails ) {
03059 global $wgUseRCPatrol, $wgUser, $wgLang;
03060 $dbw = wfGetDB( DB_MASTER );
03061
03062 if ( wfReadOnly() ) {
03063 return array( array( 'readonlytext' ) );
03064 }
03065
03066 # Get the last editor
03067 $current = Revision::newFromTitle( $this->mTitle );
03068 if ( is_null( $current ) ) {
03069 # Something wrong... no page?
03070 return array( array( 'notanarticle' ) );
03071 }
03072
03073 $from = str_replace( '_', ' ', $fromP );
03074 # User name given should match up with the top revision.
03075 # If the user was deleted then $from should be empty.
03076 if ( $from != $current->getUserText() ) {
03077 $resultDetails = array( 'current' => $current );
03078 return array( array( 'alreadyrolled',
03079 htmlspecialchars( $this->mTitle->getPrefixedText() ),
03080 htmlspecialchars( $fromP ),
03081 htmlspecialchars( $current->getUserText() )
03082 ) );
03083 }
03084
03085 # Get the last edit not by this guy...
03086 # Note: these may not be public values
03087 $user = intval( $current->getRawUser() );
03088 $user_text = $dbw->addQuotes( $current->getRawUserText() );
03089 $s = $dbw->selectRow( 'revision',
03090 array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
03091 array( 'rev_page' => $current->getPage(),
03092 "rev_user != {$user} OR rev_user_text != {$user_text}"
03093 ), __METHOD__,
03094 array( 'USE INDEX' => 'page_timestamp',
03095 'ORDER BY' => 'rev_timestamp DESC' )
03096 );
03097 if ( $s === false ) {
03098 # No one else ever edited this page
03099 return array( array( 'cantrollback' ) );
03100 } else if ( $s->rev_deleted & REVISION::DELETED_TEXT || $s->rev_deleted & REVISION::DELETED_USER ) {
03101 # Only admins can see this text
03102 return array( array( 'notvisiblerev' ) );
03103 }
03104
03105 $set = array();
03106 if ( $bot && $wgUser->isAllowed( 'markbotedits' ) ) {
03107 # Mark all reverted edits as bot
03108 $set['rc_bot'] = 1;
03109 }
03110 if ( $wgUseRCPatrol ) {
03111 # Mark all reverted edits as patrolled
03112 $set['rc_patrolled'] = 1;
03113 }
03114
03115 if ( count( $set ) ) {
03116 $dbw->update( 'recentchanges', $set,
03117 array(
03118 'rc_cur_id' => $current->getPage(),
03119 'rc_user_text' => $current->getUserText(),
03120 "rc_timestamp > '{$s->rev_timestamp}'",
03121 ), __METHOD__
03122 );
03123 }
03124
03125 # Generate the edit summary if necessary
03126 $target = Revision::newFromId( $s->rev_id );
03127 if ( empty( $summary ) ) {
03128 if ( $from == '' ) {
03129 $summary = wfMsgForContent( 'revertpage-nouser' );
03130 } else {
03131 $summary = wfMsgForContent( 'revertpage' );
03132 }
03133 }
03134
03135 # Allow the custom summary to use the same args as the default message
03136 $args = array(
03137 $target->getUserText(), $from, $s->rev_id,
03138 $wgLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ), true ),
03139 $current->getId(), $wgLang->timeanddate( $current->getTimestamp() )
03140 );
03141 $summary = wfMsgReplaceArgs( $summary, $args );
03142
03143 # Save
03144 $flags = EDIT_UPDATE;
03145
03146 if ( $wgUser->isAllowed( 'minoredit' ) )
03147 $flags |= EDIT_MINOR;
03148
03149 if ( $bot && ( $wgUser->isAllowed( 'markbotedits' ) || $wgUser->isAllowed( 'bot' ) ) )
03150 $flags |= EDIT_FORCE_BOT;
03151 # Actually store the edit
03152 $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
03153 if ( !empty( $status->value['revision'] ) ) {
03154 $revId = $status->value['revision']->getId();
03155 } else {
03156 $revId = false;
03157 }
03158
03159 wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target, $current ) );
03160
03161 $resultDetails = array(
03162 'summary' => $summary,
03163 'current' => $current,
03164 'target' => $target,
03165 'newid' => $revId
03166 );
03167 return array();
03168 }
03169
03173 public function rollback() {
03174 global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
03175 $details = null;
03176
03177 $result = $this->doRollback(
03178 $wgRequest->getVal( 'from' ),
03179 $wgRequest->getText( 'summary' ),
03180 $wgRequest->getVal( 'token' ),
03181 $wgRequest->getBool( 'bot' ),
03182 $details
03183 );
03184
03185 if ( in_array( array( 'actionthrottledtext' ), $result ) ) {
03186 $wgOut->rateLimited();
03187 return;
03188 }
03189 if ( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ) {
03190 $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
03191 $errArray = $result[0];
03192 $errMsg = array_shift( $errArray );
03193 $wgOut->addWikiMsgArray( $errMsg, $errArray );
03194 if ( isset( $details['current'] ) ) {
03195 $current = $details['current'];
03196 if ( $current->getComment() != '' ) {
03197 $wgOut->addWikiMsgArray( 'editcomment', array(
03198 $wgUser->getSkin()->formatComment( $current->getComment() ) ), array( 'replaceafter' ) );
03199 }
03200 }
03201 return;
03202 }
03203 # Display permissions errors before read-only message -- there's no
03204 # point in misleading the user into thinking the inability to rollback
03205 # is only temporary.
03206 if ( !empty( $result ) && $result !== array( array( 'readonlytext' ) ) ) {
03207 # array_diff is completely broken for arrays of arrays, sigh. Re-
03208 # move any 'readonlytext' error manually.
03209 $out = array();
03210 foreach ( $result as $error ) {
03211 if ( $error != array( 'readonlytext' ) ) {
03212 $out [] = $error;
03213 }
03214 }
03215 $wgOut->showPermissionsErrorPage( $out );
03216 return;
03217 }
03218 if ( $result == array( array( 'readonlytext' ) ) ) {
03219 $wgOut->readOnlyPage();
03220 return;
03221 }
03222
03223 $current = $details['current'];
03224 $target = $details['target'];
03225 $newId = $details['newid'];
03226 $wgOut->setPageTitle( wfMsg( 'actioncomplete' ) );
03227 $wgOut->setRobotPolicy( 'noindex,nofollow' );
03228 if ( $current->getUserText() === '' ) {
03229 $old = wfMsg( 'rev-deleted-user' );
03230 } else {
03231 $old = $wgUser->getSkin()->userLink( $current->getUser(), $current->getUserText() )
03232 . $wgUser->getSkin()->userToolLinks( $current->getUser(), $current->getUserText() );
03233 }
03234 $new = $wgUser->getSkin()->userLink( $target->getUser(), $target->getUserText() )
03235 . $wgUser->getSkin()->userToolLinks( $target->getUser(), $target->getUserText() );
03236 $wgOut->addHTML( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
03237 $wgOut->returnToMain( false, $this->mTitle );
03238
03239 if ( !$wgRequest->getBool( 'hidediff', false ) && !$wgUser->getBoolOption( 'norollbackdiff', false ) ) {
03240 $de = new DifferenceEngine( $this->mTitle, $current->getId(), $newId, false, true );
03241 $de->showDiff( '', '' );
03242 }
03243 }
03244
03245
03249 public function viewUpdates() {
03250 global $wgDeferredUpdateList, $wgDisableCounters, $wgUser;
03251 if ( wfReadOnly() ) {
03252 return;
03253 }
03254 # Don't update page view counters on views from bot users (bug 14044)
03255 if ( !$wgDisableCounters && !$wgUser->isAllowed( 'bot' ) && $this->getID() ) {
03256 Article::incViewCount( $this->getID() );
03257 $u = new SiteStatsUpdate( 1, 0, 0 );
03258 array_push( $wgDeferredUpdateList, $u );
03259 }
03260 # Update newtalk / watchlist notification status
03261 $wgUser->clearNotification( $this->mTitle );
03262 }
03263
03268 public function prepareTextForEdit( $text, $revid = null ) {
03269 if ( $this->mPreparedEdit && $this->mPreparedEdit->newText == $text && $this->mPreparedEdit->revid == $revid ) {
03270
03271 return $this->mPreparedEdit;
03272 }
03273 global $wgParser;
03274 $edit = (object)array();
03275 $edit->revid = $revid;
03276 $edit->newText = $text;
03277 $edit->pst = $this->preSaveTransform( $text );
03278 $options = $this->getParserOptions();
03279 $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $options, true, true, $revid );
03280 $edit->oldText = $this->getContent();
03281 $this->mPreparedEdit = $edit;
03282 return $edit;
03283 }
03284
03299 public function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange, $newid, $changed = true ) {
03300 global $wgDeferredUpdateList, $wgMessageCache, $wgUser, $wgEnableParserCache;
03301
03302 wfProfileIn( __METHOD__ );
03303
03304 # Parse the text
03305 # Be careful not to double-PST: $text is usually already PST-ed once
03306 if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
03307 wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
03308 $editInfo = $this->prepareTextForEdit( $text, $newid );
03309 } else {
03310 wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
03311 $editInfo = $this->mPreparedEdit;
03312 }
03313
03314 # Save it to the parser cache
03315 if ( $wgEnableParserCache ) {
03316 $popts = $this->getParserOptions();
03317 $parserCache = ParserCache::singleton();
03318 $parserCache->save( $editInfo->output, $this, $popts );
03319 }
03320
03321 # Update the links tables
03322 $u = new LinksUpdate( $this->mTitle, $editInfo->output );
03323 $u->doUpdate();
03324
03325 wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $changed ) );
03326
03327 if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
03328 if ( 0 == mt_rand( 0, 99 ) ) {
03329
03330
03331 global $wgRCMaxAge;
03332 $dbw = wfGetDB( DB_MASTER );
03333 $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
03334 $recentchanges = $dbw->tableName( 'recentchanges' );
03335 $sql = "DELETE FROM $recentchanges WHERE rc_timestamp < '{$cutoff}'";
03336 $dbw->query( $sql );
03337 }
03338 }
03339
03340 $id = $this->getID();
03341 $title = $this->mTitle->getPrefixedDBkey();
03342 $shortTitle = $this->mTitle->getDBkey();
03343
03344 if ( 0 == $id ) {
03345 wfProfileOut( __METHOD__ );
03346 return;
03347 }
03348
03349 $u = new SiteStatsUpdate( 0, 1, $this->mGoodAdjustment, $this->mTotalAdjustment );
03350 array_push( $wgDeferredUpdateList, $u );
03351 $u = new SearchUpdate( $id, $title, $text );
03352 array_push( $wgDeferredUpdateList, $u );
03353
03354 # If this is another user's talk page, update newtalk
03355 # Don't do this if $changed = false otherwise some idiot can null-edit a
03356 # load of user talk pages and piss people off, nor if it's a minor edit
03357 # by a properly-flagged bot.
03358 if ( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed
03359 && !( $minoredit && $wgUser->isAllowed( 'nominornewtalk' ) ) ) {
03360 if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
03361 $other = User::newFromName( $shortTitle, false );
03362 if ( !$other ) {
03363 wfDebug( __METHOD__ . ": invalid username\n" );
03364 } elseif ( User::isIP( $shortTitle ) ) {
03365
03366 $other->setNewtalk( true );
03367 } elseif ( $other->isLoggedIn() ) {
03368 $other->setNewtalk( true );
03369 } else {
03370 wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
03371 }
03372 }
03373 }
03374
03375 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
03376 $wgMessageCache->replace( $shortTitle, $text );
03377 }
03378
03379 wfProfileOut( __METHOD__ );
03380 }
03381
03391 public function createUpdates( $rev ) {
03392 $this->mGoodAdjustment = $this->isCountable( $rev->getText() );
03393 $this->mTotalAdjustment = 1;
03394 $this->editUpdates( $rev->getText(), $rev->getComment(),
03395 $rev->isMinor(), wfTimestamp(), $rev->getId(), true );
03396 }
03397
03406 public function setOldSubtitle( $oldid = 0 ) {
03407 global $wgLang, $wgOut, $wgUser, $wgRequest;
03408
03409 if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
03410 return;
03411 }
03412
03413 $unhide = $wgRequest->getInt( 'unhide' ) == 1 &&
03414 $wgUser->matchEditToken( $wgRequest->getVal( 'token' ), $oldid );
03415 # Cascade unhide param in links for easy deletion browsing
03416 $extraParams = array();
03417 if ( $wgRequest->getVal( 'unhide' ) ) {
03418 $extraParams['unhide'] = 1;
03419 }
03420 $revision = Revision::newFromId( $oldid );
03421
03422 $current = ( $oldid == $this->mLatest );
03423 $td = $wgLang->timeanddate( $this->mTimestamp, true );
03424 $tddate = $wgLang->date( $this->mTimestamp, true );
03425 $tdtime = $wgLang->time( $this->mTimestamp, true );
03426 $sk = $wgUser->getSkin();
03427 $lnk = $current
03428 ? wfMsgHtml( 'currentrevisionlink' )
03429 : $sk->link(
03430 $this->mTitle,
03431 wfMsgHtml( 'currentrevisionlink' ),
03432 array(),
03433 $extraParams,
03434 array( 'known', 'noclasses' )
03435 );
03436 $curdiff = $current
03437 ? wfMsgHtml( 'diff' )
03438 : $sk->link(
03439 $this->mTitle,
03440 wfMsgHtml( 'diff' ),
03441 array(),
03442 array(
03443 'diff' => 'cur',
03444 'oldid' => $oldid
03445 ) + $extraParams,
03446 array( 'known', 'noclasses' )
03447 );
03448 $prev = $this->mTitle->getPreviousRevisionID( $oldid ) ;
03449 $prevlink = $prev
03450 ? $sk->link(
03451 $this->mTitle,
03452 wfMsgHtml( 'previousrevision' ),
03453 array(),
03454 array(
03455 'direction' => 'prev',
03456 'oldid' => $oldid
03457 ) + $extraParams,
03458 array( 'known', 'noclasses' )
03459 )
03460 : wfMsgHtml( 'previousrevision' );
03461 $prevdiff = $prev
03462 ? $sk->link(
03463 $this->mTitle,
03464 wfMsgHtml( 'diff' ),
03465 array(),
03466 array(
03467 'diff' => 'prev',
03468 'oldid' => $oldid
03469 ) + $extraParams,
03470 array( 'known', 'noclasses' )
03471 )
03472 : wfMsgHtml( 'diff' );
03473 $nextlink = $current
03474 ? wfMsgHtml( 'nextrevision' )
03475 : $sk->link(
03476 $this->mTitle,
03477 wfMsgHtml( 'nextrevision' ),
03478 array(),
03479 array(
03480 'direction' => 'next',
03481 'oldid' => $oldid
03482 ) + $extraParams,
03483 array( 'known', 'noclasses' )
03484 );
03485 $nextdiff = $current
03486 ? wfMsgHtml( 'diff' )
03487 : $sk->link(
03488 $this->mTitle,
03489 wfMsgHtml( 'diff' ),
03490 array(),
03491 array(
03492 'diff' => 'next',
03493 'oldid' => $oldid
03494 ) + $extraParams,
03495 array( 'known', 'noclasses' )
03496 );
03497
03498 $cdel = '';
03499
03500 $canHide = $wgUser->isAllowed( 'deleterevision' );
03501 if ( $canHide || ( $revision->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) ) {
03502 if ( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) {
03503 $cdel = $sk->revDeleteLinkDisabled( $canHide );
03504 } else {
03505 $query = array(
03506 'type' => 'revision',
03507 'target' => $this->mTitle->getPrefixedDbkey(),
03508 'ids' => $oldid
03509 );
03510 $cdel = $sk->revDeleteLink( $query, $revision->isDeleted( File::DELETED_RESTRICTED ), $canHide );
03511 }
03512 $cdel .= ' ';
03513 }
03514
03515 # Show user links if allowed to see them. If hidden, then show them only if requested...
03516 $userlinks = $sk->revUserTools( $revision, !$unhide );
03517
03518 $m = wfMsg( 'revision-info-current' );
03519 $infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-'
03520 ? 'revision-info-current'
03521 : 'revision-info';
03522
03523 $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" .
03524 wfMsgExt(
03525 $infomsg,
03526 array( 'parseinline', 'replaceafter' ),
03527 $td,
03528 $userlinks,
03529 $revision->getID(),
03530 $tddate,
03531 $tdtime,
03532 $revision->getUser()
03533 ) .
03534 "</div>\n" .
03535 "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ),
03536 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
03537 $wgOut->setSubtitle( $r );
03538 }
03539
03546 public function preSaveTransform( $text ) {
03547 global $wgParser, $wgUser;
03548 return $wgParser->preSaveTransform( $text, $this->mTitle, $wgUser, ParserOptions::newFromUser( $wgUser ) );
03549 }
03550
03551
03552
03558 protected function tryFileCache() {
03559 static $called = false;
03560 if ( $called ) {
03561 wfDebug( "Article::tryFileCache(): called twice!?\n" );
03562 return false;
03563 }
03564 $called = true;
03565 if ( $this->isFileCacheable() ) {
03566 $cache = new HTMLFileCache( $this->mTitle );
03567 if ( $cache->isFileCacheGood( $this->mTouched ) ) {
03568 wfDebug( "Article::tryFileCache(): about to load file\n" );
03569 $cache->loadFromFileCache();
03570 return true;
03571 } else {
03572 wfDebug( "Article::tryFileCache(): starting buffer\n" );
03573 ob_start( array( &$cache, 'saveToFileCache' ) );
03574 }
03575 } else {
03576 wfDebug( "Article::tryFileCache(): not cacheable\n" );
03577 }
03578 return false;
03579 }
03580
03585 public function isFileCacheable() {
03586 $cacheable = false;
03587 if ( HTMLFileCache::useFileCache() ) {
03588 $cacheable = $this->getID() && !$this->mRedirectedFrom;
03589
03590 if ( $cacheable ) {
03591 $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
03592 }
03593 }
03594 return $cacheable;
03595 }
03596
03601 public function checkTouched() {
03602 if ( !$this->mDataLoaded ) {
03603 $this->loadPageData();
03604 }
03605 return !$this->mIsRedirect;
03606 }
03607
03611 public function getTouched() {
03612 # Ensure that page data has been loaded
03613 if ( !$this->mDataLoaded ) {
03614 $this->loadPageData();
03615 }
03616 return $this->mTouched;
03617 }
03618
03622 public function getLatest() {
03623 if ( !$this->mDataLoaded ) {
03624 $this->loadPageData();
03625 }
03626 return (int)$this->mLatest;
03627 }
03628
03638 public function quickEdit( $text, $comment = '', $minor = 0 ) {
03639 wfProfileIn( __METHOD__ );
03640
03641 $dbw = wfGetDB( DB_MASTER );
03642 $revision = new Revision( array(
03643 'page' => $this->getId(),
03644 'text' => $text,
03645 'comment' => $comment,
03646 'minor_edit' => $minor ? 1 : 0,
03647 ) );
03648 $revision->insertOn( $dbw );
03649 $this->updateRevisionOn( $dbw, $revision );
03650
03651 wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $wgUser ) );
03652
03653 wfProfileOut( __METHOD__ );
03654 }
03655
03661 public static function incViewCount( $id ) {
03662 $id = intval( $id );
03663 global $wgHitcounterUpdateFreq;
03664
03665 $dbw = wfGetDB( DB_MASTER );
03666 $pageTable = $dbw->tableName( 'page' );
03667 $hitcounterTable = $dbw->tableName( 'hitcounter' );
03668 $acchitsTable = $dbw->tableName( 'acchits' );
03669 $dbType = $dbw->getType();
03670
03671 if ( $wgHitcounterUpdateFreq <= 1 || $dbType == 'sqlite' ) {
03672 $dbw->query( "UPDATE $pageTable SET page_counter = page_counter + 1 WHERE page_id = $id" );
03673 return;
03674 }
03675
03676 # Not important enough to warrant an error page in case of failure
03677 $oldignore = $dbw->ignoreErrors( true );
03678
03679 $dbw->query( "INSERT INTO $hitcounterTable (hc_id) VALUES ({$id})" );
03680
03681 $checkfreq = intval( $wgHitcounterUpdateFreq / 25 + 1 );
03682 if ( ( rand() % $checkfreq != 0 ) or ( $dbw->lastErrno() != 0 ) ) {
03683 # Most of the time (or on SQL errors), skip row count check
03684 $dbw->ignoreErrors( $oldignore );
03685 return;
03686 }
03687
03688 $res = $dbw->query( "SELECT COUNT(*) as n FROM $hitcounterTable" );
03689 $row = $dbw->fetchObject( $res );
03690 $rown = intval( $row->n );
03691 if ( $rown >= $wgHitcounterUpdateFreq ) {
03692 wfProfileIn( 'Article::incViewCount-collect' );
03693 $old_user_abort = ignore_user_abort( true );
03694
03695 $dbw->lockTables( array(), array( 'hitcounter' ), __METHOD__, false );
03696 $tabletype = $dbType == 'mysql' ? "ENGINE=HEAP " : '';
03697 $dbw->query( "CREATE TEMPORARY TABLE $acchitsTable $tabletype AS " .
03698 "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable " .
03699 'GROUP BY hc_id', __METHOD__ );
03700 $dbw->delete( 'hitcounter', '*', __METHOD__ );
03701 $dbw->unlockTables( __METHOD__ );
03702 if ( $dbType == 'mysql' ) {
03703 $dbw->query( "UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n " .
03704 'WHERE page_id = hc_id', __METHOD__ );
03705 }
03706 else {
03707 $dbw->query( "UPDATE $pageTable SET page_counter=page_counter + hc_n " .
03708 "FROM $acchitsTable WHERE page_id = hc_id", __METHOD__ );
03709 }
03710 $dbw->query( "DROP TABLE $acchitsTable", __METHOD__ );
03711
03712 ignore_user_abort( $old_user_abort );
03713 wfProfileOut( 'Article::incViewCount-collect' );
03714 }
03715 $dbw->ignoreErrors( $oldignore );
03716 }
03717
03729 public static function onArticleCreate( $title ) {
03730 # Update existence markers on article/talk tabs...
03731 if ( $title->isTalkPage() ) {
03732 $other = $title->getSubjectPage();
03733 } else {
03734 $other = $title->getTalkPage();
03735 }
03736 $other->invalidateCache();
03737 $other->purgeSquid();
03738
03739 $title->touchLinks();
03740 $title->purgeSquid();
03741 $title->deleteTitleProtection();
03742 }
03743
03744 public static function onArticleDelete( $title ) {
03745 global $wgMessageCache;
03746 # Update existence markers on article/talk tabs...
03747 if ( $title->isTalkPage() ) {
03748 $other = $title->getSubjectPage();
03749 } else {
03750 $other = $title->getTalkPage();
03751 }
03752 $other->invalidateCache();
03753 $other->purgeSquid();
03754
03755 $title->touchLinks();
03756 $title->purgeSquid();
03757
03758 # File cache
03759 HTMLFileCache::clearFileCache( $title );
03760
03761 # Messages
03762 if ( $title->getNamespace() == NS_MEDIAWIKI ) {
03763 $wgMessageCache->replace( $title->getDBkey(), false );
03764 }
03765 # Images
03766 if ( $title->getNamespace() == NS_FILE ) {
03767 $update = new HTMLCacheUpdate( $title, 'imagelinks' );
03768 $update->doUpdate();
03769 }
03770 # User talk pages
03771 if ( $title->getNamespace() == NS_USER_TALK ) {
03772 $user = User::newFromName( $title->getText(), false );
03773 $user->setNewtalk( false );
03774 }
03775 # Image redirects
03776 RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
03777 }
03778
03782 public static function onArticleEdit( $title, $flags = '' ) {
03783 global $wgDeferredUpdateList;
03784
03785
03786 $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
03787
03788
03789 $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
03790
03791 # Purge squid for this page only
03792 $title->purgeSquid();
03793
03794 # Clear file cache for this page only
03795 HTMLFileCache::clearFileCache( $title );
03796 }
03797
03804 public function revert() {
03805 global $wgOut;
03806 $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
03807 }
03808
03813 public function info() {
03814 global $wgLang, $wgOut, $wgAllowPageInfo, $wgUser;
03815
03816 if ( !$wgAllowPageInfo ) {
03817 $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
03818 return;
03819 }
03820
03821 $page = $this->mTitle->getSubjectPage();
03822
03823 $wgOut->setPagetitle( $page->getPrefixedText() );
03824 $wgOut->setPageTitleActionText( wfMsg( 'info_short' ) );
03825 $wgOut->setSubtitle( wfMsgHtml( 'infosubtitle' ) );
03826
03827 if ( !$this->mTitle->exists() ) {
03828 $wgOut->addHTML( '<div class="noarticletext">' );
03829 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
03830
03831
03832 $wgOut->addHTML( htmlspecialchars( wfMsgWeirdKey( $this->mTitle->getText() ) ) );
03833 } else {
03834 $msg = $wgUser->isLoggedIn()
03835 ? 'noarticletext'
03836 : 'noarticletextanon';
03837 $wgOut->addHTML( wfMsgExt( $msg, 'parse' ) );
03838 }
03839 $wgOut->addHTML( '</div>' );
03840 } else {
03841 $dbr = wfGetDB( DB_SLAVE );
03842 $wl_clause = array(
03843 'wl_title' => $page->getDBkey(),
03844 'wl_namespace' => $page->getNamespace() );
03845 $numwatchers = $dbr->selectField(
03846 'watchlist',
03847 'COUNT(*)',
03848 $wl_clause,
03849 __METHOD__,
03850 $this->getSelectOptions() );
03851
03852 $pageInfo = $this->pageCountInfo( $page );
03853 $talkInfo = $this->pageCountInfo( $page->getTalkPage() );
03854
03855 $wgOut->addHTML( "<ul><li>" . wfMsg( "numwatchers", $wgLang->formatNum( $numwatchers ) ) . '</li>' );
03856 $wgOut->addHTML( "<li>" . wfMsg( 'numedits', $wgLang->formatNum( $pageInfo['edits'] ) ) . '</li>' );
03857 if ( $talkInfo ) {
03858 $wgOut->addHTML( '<li>' . wfMsg( "numtalkedits", $wgLang->formatNum( $talkInfo['edits'] ) ) . '</li>' );
03859 }
03860 $wgOut->addHTML( '<li>' . wfMsg( "numauthors", $wgLang->formatNum( $pageInfo['authors'] ) ) . '</li>' );
03861 if ( $talkInfo ) {
03862 $wgOut->addHTML( '<li>' . wfMsg( 'numtalkauthors', $wgLang->formatNum( $talkInfo['authors'] ) ) . '</li>' );
03863 }
03864 $wgOut->addHTML( '</ul>' );
03865 }
03866 }
03867
03875 public function pageCountInfo( $title ) {
03876 $id = $title->getArticleId();
03877 if ( $id == 0 ) {
03878 return false;
03879 }
03880 $dbr = wfGetDB( DB_SLAVE );
03881 $rev_clause = array( 'rev_page' => $id );
03882 $edits = $dbr->selectField(
03883 'revision',
03884 'COUNT(rev_page)',
03885 $rev_clause,
03886 __METHOD__,
03887 $this->getSelectOptions()
03888 );
03889 $authors = $dbr->selectField(
03890 'revision',
03891 'COUNT(DISTINCT rev_user_text)',
03892 $rev_clause,
03893 __METHOD__,
03894 $this->getSelectOptions()
03895 );
03896 return array( 'edits' => $edits, 'authors' => $authors );
03897 }
03898
03905 public function getUsedTemplates() {
03906 $result = array();
03907 $id = $this->mTitle->getArticleID();
03908 if ( $id == 0 ) {
03909 return array();
03910 }
03911 $dbr = wfGetDB( DB_SLAVE );
03912 $res = $dbr->select( array( 'templatelinks' ),
03913 array( 'tl_namespace', 'tl_title' ),
03914 array( 'tl_from' => $id ),
03915 __METHOD__ );
03916 if ( $res !== false ) {
03917 foreach ( $res as $row ) {
03918 $result[] = Title::makeTitle( $row->tl_namespace, $row->tl_title );
03919 }
03920 }
03921 $dbr->freeResult( $res );
03922 return $result;
03923 }
03924
03931 public function getHiddenCategories() {
03932 $result = array();
03933 $id = $this->mTitle->getArticleID();
03934 if ( $id == 0 ) {
03935 return array();
03936 }
03937 $dbr = wfGetDB( DB_SLAVE );
03938 $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
03939 array( 'cl_to' ),
03940 array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
03941 'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ),
03942 __METHOD__ );
03943 if ( $res !== false ) {
03944 foreach ( $res as $row ) {
03945 $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
03946 }
03947 }
03948 $dbr->freeResult( $res );
03949 return $result;
03950 }
03951
03959 public static function getAutosummary( $oldtext, $newtext, $flags ) {
03960 # Decide what kind of autosummary is needed.
03961
03962 # Redirect autosummaries
03963 $ot = Title::newFromRedirect( $oldtext );
03964 $rt = Title::newFromRedirect( $newtext );
03965 if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
03966 return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
03967 }
03968
03969 # New page autosummaries
03970 if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
03971 # If they're making a new article, give its text, truncated, in the summary.
03972 global $wgContLang;
03973 $truncatedtext = $wgContLang->truncate(
03974 str_replace( "\n", ' ', $newtext ),
03975 max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
03976 return wfMsgForContent( 'autosumm-new', $truncatedtext );
03977 }
03978
03979 # Blanking autosummaries
03980 if ( $oldtext != '' && $newtext == '' ) {
03981 return wfMsgForContent( 'autosumm-blank' );
03982 } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
03983 # Removing more than 90% of the article
03984 global $wgContLang;
03985 $truncatedtext = $wgContLang->truncate(
03986 $newtext,
03987 max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
03988 return wfMsgForContent( 'autosumm-replace', $truncatedtext );
03989 }
03990
03991 # If we reach this point, there's no applicable autosummary for our case, so our
03992 # autosummary is empty.
03993 return '';
03994 }
03995
04004 public function outputWikiText( $text, $cache = true, $parserOptions = false ) {
04005 global $wgOut;
04006
04007 $this->mParserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions );
04008 $wgOut->addParserOutput( $this->mParserOutput );
04009 }
04010
04016 public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) {
04017 global $wgParser, $wgOut, $wgEnableParserCache, $wgUseFileCache;
04018
04019 if ( !$parserOptions ) {
04020 $parserOptions = $this->getParserOptions();
04021 }
04022
04023 $time = - wfTime();
04024 $this->mParserOutput = $wgParser->parse( $text, $this->mTitle,
04025 $parserOptions, true, true, $this->getRevIdFetched() );
04026 $time += wfTime();
04027
04028 # Timing hack
04029 if ( $time > 3 ) {
04030 wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
04031 $this->mTitle->getPrefixedDBkey() ) );
04032 }
04033
04034 if ( $wgEnableParserCache && $cache && $this && $this->mParserOutput->getCacheTime() != -1 ) {
04035 $parserCache = ParserCache::singleton();
04036 $parserCache->save( $this->mParserOutput, $this, $parserOptions );
04037 }
04038
04039
04040
04041 if ( $this->mParserOutput->getCacheTime() == -1 || $this->mParserOutput->containsOldMagic() ) {
04042 $wgUseFileCache = false;
04043 }
04044 $this->doCascadeProtectionUpdates( $this->mParserOutput );
04045 return $this->mParserOutput;
04046 }
04047
04051 public function getParserOptions() {
04052 global $wgUser;
04053 if ( !$this->mParserOptions ) {
04054 $this->mParserOptions = new ParserOptions( $wgUser );
04055 $this->mParserOptions->setTidy( true );
04056 $this->mParserOptions->enableLimitReport();
04057 }
04058 return $this->mParserOptions;
04059 }
04060
04061 protected function doCascadeProtectionUpdates( $parserOutput ) {
04062 if ( !$this->isCurrent() || wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
04063 return;
04064 }
04065
04066
04067
04068
04069
04070
04071
04072
04073 # Get templates from templatelinks
04074 $id = $this->mTitle->getArticleID();
04075
04076 $tlTemplates = array();
04077
04078 $dbr = wfGetDB( DB_SLAVE );
04079 $res = $dbr->select( array( 'templatelinks' ),
04080 array( 'tl_namespace', 'tl_title' ),
04081 array( 'tl_from' => $id ),
04082 __METHOD__ );
04083
04084 global $wgContLang;
04085 foreach ( $res as $row ) {
04086 $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
04087 }
04088
04089 # Get templates from parser output.
04090 $poTemplates = array();
04091 foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
04092 foreach ( $templates as $dbk => $id ) {
04093 $poTemplates["$ns:$dbk"] = true;
04094 }
04095 }
04096
04097 # Get the diff
04098 # Note that we simulate array_diff_key in PHP <5.0.x
04099 $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
04100
04101 if ( count( $templates_diff ) > 0 ) {
04102 # Whee, link updates time.
04103 $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
04104 $u->doUpdate();
04105 }
04106 }
04107
04116 public function updateCategoryCounts( $added, $deleted ) {
04117 $ns = $this->mTitle->getNamespace();
04118 $dbw = wfGetDB( DB_MASTER );
04119
04120 # First make sure the rows exist. If one of the "deleted" ones didn't
04121 # exist, we might legitimately not create it, but it's simpler to just
04122 # create it and then give it a negative value, since the value is bogus
04123 # anyway.
04124 #
04125 # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
04126 $insertCats = array_merge( $added, $deleted );
04127 if ( !$insertCats ) {
04128 # Okay, nothing to do
04129 return;
04130 }
04131 $insertRows = array();
04132 foreach ( $insertCats as $cat ) {
04133 $insertRows[] = array(
04134 'cat_id' => $dbw->nextSequenceValue( 'category_cat_id_seq' ),
04135 'cat_title' => $cat
04136 );
04137 }
04138 $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
04139
04140 $addFields = array( 'cat_pages = cat_pages + 1' );
04141 $removeFields = array( 'cat_pages = cat_pages - 1' );
04142 if ( $ns == NS_CATEGORY ) {
04143 $addFields[] = 'cat_subcats = cat_subcats + 1';
04144 $removeFields[] = 'cat_subcats = cat_subcats - 1';
04145 } elseif ( $ns == NS_FILE ) {
04146 $addFields[] = 'cat_files = cat_files + 1';
04147 $removeFields[] = 'cat_files = cat_files - 1';
04148 }
04149
04150 if ( $added ) {
04151 $dbw->update(
04152 'category',
04153 $addFields,
04154 array( 'cat_title' => $added ),
04155 __METHOD__
04156 );
04157 }
04158 if ( $deleted ) {
04159 $dbw->update(
04160 'category',
04161 $removeFields,
04162 array( 'cat_title' => $deleted ),
04163 __METHOD__
04164 );
04165 }
04166 }
04167
04172 function getParserOutput( $oldid = null ) {
04173 global $wgEnableParserCache, $wgUser, $wgOut;
04174
04175
04176 $useParserCache = $wgEnableParserCache &&
04177 intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 &&
04178 $this->exists() &&
04179 $oldid === null;
04180
04181 wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
04182 if ( $wgUser->getOption( 'stubthreshold' ) ) {
04183 wfIncrStats( 'pcache_miss_stub' );
04184 }
04185
04186 $parserOutput = false;
04187 if ( $useParserCache ) {
04188 $parserOutput = ParserCache::singleton()->get( $this, $this->getParserOptions() );
04189 }
04190
04191 if ( $parserOutput === false ) {
04192
04193 $rev = Revision::newFromTitle( $this->getTitle(), $oldid );
04194
04195 return $this->getOutputFromWikitext( $rev->getText(), $useParserCache );
04196 } else {
04197 return $parserOutput;
04198 }
04199 }
04200 }