diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2008-08-15 01:29:47 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2008-08-15 01:29:47 +0200 |
commit | 370e83bb0dfd0c70de268c93bf07ad5ee0897192 (patch) | |
tree | 491674f4c242e4d6ba0d04eafa305174c35a3391 /includes/Article.php | |
parent | f4debf0f12d0524d2b2427c55ea3f16b680fad97 (diff) |
Update auf 1.13.0
Diffstat (limited to 'includes/Article.php')
-rw-r--r-- | includes/Article.php | 708 |
1 files changed, 487 insertions, 221 deletions
diff --git a/includes/Article.php b/includes/Article.php index 0544db7d..4d8277bb 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -1,6 +1,7 @@ <?php /** * File for articles + * @file */ /** @@ -34,6 +35,8 @@ class Article { var $mTouched; //!< var $mUser; //!< var $mUserText; //!< + var $mRedirectTarget; //!< + var $mIsRedirect; /**@}}*/ /** @@ -57,12 +60,69 @@ class Article { } /** + * If this page is a redirect, get its target + * + * The target will be fetched from the redirect table if possible. + * If this page doesn't have an entry there, call insertRedirect() + * @return mixed Title object, or null if this page is not a redirect + */ + public function getRedirectTarget() { + if(!$this->mTitle || !$this->mTitle->isRedirect()) + return null; + if(!is_null($this->mRedirectTarget)) + return $this->mRedirectTarget; + + # Query the redirect table + $dbr = wfGetDB(DB_SLAVE); + $res = $dbr->select('redirect', + array('rd_namespace', 'rd_title'), + array('rd_from' => $this->getID()), + __METHOD__ + ); + $row = $dbr->fetchObject($res); + if($row) + return $this->mRedirectTarget = Title::makeTitle($row->rd_namespace, $row->rd_title); + + # This page doesn't have an entry in the redirect table + return $this->mRedirectTarget = $this->insertRedirect(); + } + + /** + * Insert an entry for this page into the redirect table. + * + * Don't call this function directly unless you know what you're doing. + * @return Title object + */ + public function insertRedirect() { + $retval = Title::newFromRedirect($this->getContent()); + if(!$retval) + return null; + $dbw = wfGetDB(DB_MASTER); + $dbw->replace('redirect', array('rd_from'), array( + 'rd_from' => $this->getID(), + 'rd_namespace' => $retval->getNamespace(), + 'rd_title' => $retval->getDBKey() + ), __METHOD__); + return $retval; + } + + /** + * Get the Title object this page redirects to + * * @return mixed false, Title of in-wiki target, or string with URL */ - function followRedirect() { + public function followRedirect() { $text = $this->getContent(); + return self::followRedirectText( $text ); + } + + /** + * Get the Title object this text redirects to + * + * @return mixed false, Title of in-wiki target, or string with URL + */ + public function followRedirectText( $text ) { $rt = Title::newFromRedirect( $text ); - # process if title object is valid and not special:userlogout if( $rt ) { if( $rt->getInterwiki() != '' ) { @@ -92,7 +152,6 @@ class Article { return $rt; } } - // No or invalid redirect return false; } @@ -114,6 +173,7 @@ class Article { $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded $this->mRedirectedFrom = null; # Title object if set + $this->mRedirectTarget = null; # Title object if set $this->mUserText = $this->mTimestamp = $this->mComment = ''; $this->mGoodAdjustment = $this->mTotalAdjustment = 0; @@ -138,7 +198,7 @@ class Article { * @return Return the text of this revision */ function getContent() { - global $wgUser, $wgOut; + global $wgUser, $wgOut, $wgMessageCache; wfProfileIn( __METHOD__ ); @@ -147,12 +207,13 @@ class Article { $wgOut->setRobotpolicy( 'noindex,nofollow' ); if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { + $wgMessageCache->loadAllMessages(); $ret = wfMsgWeirdKey ( $this->mTitle->getText() ) ; } else { $ret = wfMsg( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon' ); } - return "<div class='noarticletext'>$ret</div>"; + return "<div class='noarticletext'>\n$ret\n</div>"; } else { $this->loadContent(); wfProfileOut( __METHOD__ ); @@ -298,13 +359,13 @@ class Article { */ function loadPageData( $data = 'fromdb' ) { if ( $data === 'fromdb' ) { - $dbr = $this->getDB(); + $dbr = wfGetDB( DB_MASTER ); $data = $this->pageDataFromId( $dbr, $this->getId() ); } - $lc =& LinkCache::singleton(); + $lc = LinkCache::singleton(); if ( $data ) { - $lc->addGoodLinkObj( $data->page_id, $this->mTitle ); + $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect ); $this->mTitle->mArticleID = $data->page_id; @@ -336,15 +397,13 @@ class Article { return $this->mContent; } - $dbr = $this->getDB(); + $dbr = wfGetDB( DB_MASTER ); # Pre-fill content with error message so that if something # fails we'll have something telling us what we intended. $t = $this->mTitle->getPrefixedText(); - if( $oldid ) { - $t .= ',oldid='.$oldid; - } - $this->mContent = wfMsg( 'missingarticle', $t ) ; + $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : ''; + $this->mContent = wfMsg( 'missing-article', $t, $d ) ; if( $oldid ) { $revision = Revision::newFromId( $oldid ); @@ -377,15 +436,14 @@ class Article { // FIXME: Horrible, horrible! This content-loading interface just plain sucks. // We should instead work with the Revision object when we need it... - $this->mContent = $revision->userCan( Revision::DELETED_TEXT ) ? $revision->getRawText() : ""; - //$this->mContent = $revision->getText(); + $this->mContent = $revision->revText(); // Loads if user is allowed $this->mUser = $revision->getUser(); $this->mUserText = $revision->getUserText(); $this->mComment = $revision->getComment(); $this->mTimestamp = wfTimestamp( TS_MW, $revision->getTimestamp() ); - $this->mRevIdFetched = $revision->getID(); + $this->mRevIdFetched = $revision->getId(); $this->mContentLoaded = true; $this->mRevision =& $revision; @@ -407,8 +465,10 @@ class Article { * Get the database which should be used for reads * * @return Database + * @deprecated - just call wfGetDB( DB_MASTER ) instead */ function getDB() { + wfDeprecated( __METHOD__ ); return wfGetDB( DB_MASTER ); } @@ -490,6 +550,10 @@ class Article { */ function isRedirect( $text = false ) { if ( $text === false ) { + if ( $this->mDataLoaded ) + return $this->mIsRedirect; + + // Apparently loadPageData was never called $this->loadContent(); $titleObj = Title::newFromRedirect( $this->fetchContent() ); } else { @@ -526,14 +590,14 @@ class Article { $id = $this->getID(); if ( 0 == $id ) return; - $this->mLastRevision = Revision::loadFromPageId( $this->getDB(), $id ); + $this->mLastRevision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id ); if( !is_null( $this->mLastRevision ) ) { $this->mUser = $this->mLastRevision->getUser(); $this->mUserText = $this->mLastRevision->getUserText(); $this->mTimestamp = $this->mLastRevision->getTimestamp(); $this->mComment = $this->mLastRevision->getComment(); $this->mMinorEdit = $this->mLastRevision->isMinor(); - $this->mRevIdFetched = $this->mLastRevision->getID(); + $this->mRevIdFetched = $this->mLastRevision->getId(); } } @@ -571,7 +635,6 @@ class Article { } /** - * @todo Document, fixme $offset never used. * @param $limit Integer: default 0. * @param $offset Integer: default 0. */ @@ -593,6 +656,8 @@ class Article { ORDER BY timestamp DESC"; if ($limit > 0) { $sql .= ' LIMIT '.$limit; } + if ($offset > 0) { $sql .= ' OFFSET '.$offset; } + $sql .= ' '. $this->getSelectOptions(); $res = $dbr->query($sql, __METHOD__); @@ -609,7 +674,7 @@ class Article { * This is the default action of the script: just view the page of * the given title. */ - function view() { + function view() { global $wgUser, $wgOut, $wgRequest, $wgContLang; global $wgEnableParserCache, $wgStylePath, $wgParser; global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies; @@ -618,7 +683,7 @@ class Article { wfProfileIn( __METHOD__ ); - $parserCache =& ParserCache::singleton(); + $parserCache = ParserCache::singleton(); $ns = $this->mTitle->getNamespace(); # shortcut # Get variables from query string @@ -689,12 +754,7 @@ class Article { } # Should the parser cache be used? - $pcache = $wgEnableParserCache - && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 - && $this->exists() - && empty( $oldid ) - && !$this->mTitle->isCssOrJsPage() - && !$this->mTitle->isCssJsSubpage(); + $pcache = $this->useParserCache( $oldid ); wfDebug( 'Article::view using parser cache: ' . ($pcache ? 'yes' : 'no' ) . "\n" ); if ( $wgUser->getOption( 'stubthreshold' ) ) { wfIncrStats( 'pcache_miss_stub' ); @@ -705,7 +765,6 @@ class Article { // This is an internally redirected page view. // We'll need a backlink to the source page for navigation. if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) { - $sk = $wgUser->getSkin(); $redir = $sk->makeKnownLinkObj( $this->mRedirectedFrom, '', 'redirect=no' ); $s = wfMsg( 'redirectedfrom', $redir ); $wgOut->setSubtitle( $s ); @@ -722,7 +781,6 @@ class Article { // If it was reported from a trusted site, supply a backlink. global $wgRedirectSources; if( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { - $sk = $wgUser->getSkin(); $redir = $sk->makeExternalLink( $rdfrom, $rdfrom ); $s = wfMsg( 'redirectedfrom', $redir ); $wgOut->setSubtitle( $s ); @@ -740,16 +798,17 @@ class Article { $outputDone = true; } } + # Fetch content and check for errors if ( !$outputDone ) { $text = $this->getContent(); if ( $text === false ) { # Failed to load, replace text with error message $t = $this->mTitle->getPrefixedText(); if( $oldid ) { - $t .= ',oldid='.$oldid; - $text = wfMsg( 'missingarticle', $t ); + $d = wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ); + $text = wfMsg( 'missing-article', $t, $d ); } else { - $text = wfMsg( 'noarticletext', $t ); + $text = wfMsg( 'noarticletext' ); } } @@ -757,11 +816,11 @@ class Article { if ( !$this->mTitle->userCanRead() ) { $wgOut->loginToUse(); $wgOut->output(); + wfProfileOut( __METHOD__ ); exit; } # We're looking at an old revision - if ( !empty( $oldid ) ) { $wgOut->setRobotpolicy( 'noindex,nofollow' ); if( is_null( $this->mRevision ) ) { @@ -772,6 +831,7 @@ class Article { if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) { $wgOut->addWikiMsg( 'rev-deleted-text-permission' ); $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); + wfProfileOut( __METHOD__ ); return; } else { $wgOut->addWikiMsg( 'rev-deleted-text-view' ); @@ -779,16 +839,13 @@ class Article { } } } - } - } - if( !$outputDone ) { - $wgOut->setRevisionId( $this->getRevIdFetched() ); + $wgOut->setRevisionId( $this->getRevIdFetched() ); + // Pages containing custom CSS or JavaScript get special treatment if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) { $wgOut->addHtml( wfMsgExt( 'clearyourcache', 'parse' ) ); - // Give hooks a chance to customise the output if( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) { // Wrap the whole lot in a <pre> and don't parse @@ -798,22 +855,9 @@ class Article { $wgOut->addHtml( htmlspecialchars( $this->mContent ) ); $wgOut->addHtml( "\n</pre>\n" ); } - - } - - elseif ( $rt = Title::newFromRedirect( $text ) ) { - # Display redirect - $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr'; - $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png'; - # Don't overwrite the subtitle if this was an old revision - if( !$wasRedirected && $this->isCurrent() ) { - $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) ); - } - $link = $sk->makeLinkObj( $rt, $rt->getFullText() ); - - $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT " />' . - '<span class="redirectText">'.$link.'</span>' ); - + } else if ( $rt = Title::newFromRedirect( $text ) ) { + # Don't append the subtitle if this was an old revision + $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ); $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser)); $wgOut->addParserOutputNoText( $parseout ); } else if ( $pcache ) { @@ -840,7 +884,6 @@ class Article { if( !$this->isCurrent() ) { $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); } - } } /* title may have been set from the cache */ @@ -850,8 +893,7 @@ class Article { } # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page - if( $ns == NS_USER_TALK && - User::isIP( $this->mTitle->getText() ) ) { + if( $ns == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) { $wgOut->addWikiMsg('anontalkpagetext'); } @@ -875,6 +917,41 @@ class Article { $this->viewUpdates(); wfProfileOut( __METHOD__ ); } + + /* + * Should the parser cache be used? + */ + protected function useParserCache( $oldid ) { + global $wgUser, $wgEnableParserCache; + + return $wgEnableParserCache + && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 + && $this->exists() + && empty( $oldid ) + && !$this->mTitle->isCssOrJsPage() + && !$this->mTitle->isCssJsSubpage(); + } + + protected function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) { + global $wgParser, $wgOut, $wgContLang, $wgStylePath, $wgUser; + + # Display redirect + $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr'; + $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png'; + + if( $appendSubtitle ) { + $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) ); + } + $sk = $wgUser->getSkin(); + if ( $forceKnown ) + $link = $sk->makeKnownLinkObj( $target, htmlspecialchars( $target->getFullText() ) ); + else + $link = $sk->makeLinkObj( $target, htmlspecialchars( $target->getFullText() ) ); + + $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT " />' . + '<span class="redirectText">'.$link.'</span>' ); + + } function addTrackbacks() { global $wgOut, $wgUser; @@ -897,6 +974,7 @@ class Article { . $o->tb_id . "&token=" . urlencode( $wgUser->editToken() ) ); $rmvtxt = wfMsg( 'trackbackremove', htmlspecialchars( $delurl ) ); } + $tbtext .= "\n"; $tbtext .= wfMsg(strlen($o->tb_ex) ? 'trackbackexcerpt' : 'trackback', $o->tb_title, $o->tb_url, @@ -1067,7 +1145,6 @@ class Article { $result = $dbw->affectedRows() != 0; if ($result) { - // FIXME: Should the result from updateRedirectOn() be returned instead? $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect ); } @@ -1112,6 +1189,8 @@ class Article { $dbw->delete( 'redirect', $where, __METHOD__); } + if( $this->getTitle()->getNamespace() == NS_IMAGE ) + RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() ); wfProfileOut( __METHOD__ ); return ( $dbw->affectedRows() != 0 ); } @@ -1252,10 +1331,10 @@ class Article { } } - $extraq = ''; // Give extensions a chance to modify URL query on update - wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraq ) ); + $extraQuery = ''; // Give extensions a chance to modify URL query on update + wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraQuery ) ); - $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraq ); + $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraQuery ); } return $good; } @@ -1291,11 +1370,12 @@ class Article { * EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception * to be thrown from the Database. These two conditions are also possible with auto-detection due * to MediaWiki's performance-optimised locking strategy. + * @param $baseRevId, the revision ID this edit was based off, if any * * @return bool success */ - function doEdit( $text, $summary, $flags = 0 ) { - global $wgUser, $wgDBtransactions; + function doEdit( $text, $summary, $flags = 0, $baseRevId = false ) { + global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries; wfProfileIn( __METHOD__ ); $good = true; @@ -1325,9 +1405,10 @@ class Article { $oldtext = $this->getContent(); $oldsize = strlen( $oldtext ); - # Provide autosummaries if one is not provided. - if ($flags & EDIT_AUTOSUMMARY && $summary == '') + # Provide autosummaries if one is not provided and autosummaries are enabled. + if( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) { $summary = $this->getAutosummary( $oldtext, $text, $flags ); + } $editInfo = $this->prepareTextForEdit( $text ); $text = $editInfo->pst; @@ -1346,7 +1427,7 @@ class Article { $lastRevision = 0; $revisionId = 0; - + $changed = ( strcmp( $text, $oldtext ) != 0 ); if ( $changed ) { @@ -1368,7 +1449,8 @@ class Article { 'page' => $this->getId(), 'comment' => $summary, 'minor_edit' => $isminor, - 'text' => $text + 'text' => $text, + 'parent_id' => $lastRevision ) ); $dbw->begin(); @@ -1382,6 +1464,8 @@ class Article { $good = false; $dbw->rollback(); } else { + wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId ) ); + # Update recentchanges if( !( $flags & EDIT_SUPPRESS_RC ) ) { $rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary, @@ -1414,7 +1498,7 @@ class Article { # Invalidate cache of this article and all pages using this article # as a template. Partly deferred. Article::onArticleEdit( $this->mTitle ); - + # Update links tables, site stats, etc. $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed ); } @@ -1446,6 +1530,8 @@ class Article { # Update the page record with revision data $this->updateRevisionOn( $dbw, $revision, 0 ); + + wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, false) ); if( !( $flags & EDIT_SUPPRESS_RC ) ) { $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot, @@ -1495,15 +1581,16 @@ class Article { * * @param boolean $noRedir Add redirect=no * @param string $sectionAnchor section to redirect to, including "#" + * @param string $extraQuery, extra query params */ - function doRedirect( $noRedir = false, $sectionAnchor = '', $extraq = '' ) { + function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) { global $wgOut; if ( $noRedir ) { $query = 'redirect=no'; - if( $extraq ) + if( $extraQuery ) $query .= "&$query"; } else { - $query = $extraq; + $query = $extraQuery; } $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor ); } @@ -1518,8 +1605,8 @@ class Article { # Check patrol config options if ( !($wgUseNPPatrol || $wgUseRCPatrol)) { - $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); - return; + $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); + return; } # If we haven't been given an rc_id value, we can't do anything @@ -1527,18 +1614,18 @@ class Article { $rc = $rcid ? RecentChange::newFromId($rcid) : null; if ( is_null ( $rc ) ) { - $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); + $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); return; } - if ( !$wgUseRCPatrol && $rc->mAttribs['rc_type'] != RC_NEW) { + if ( !$wgUseRCPatrol && $rc->getAttribute( 'rc_type' ) != RC_NEW) { // Only new pages can be patrolled if the general patrolling is off....??? // @fixme -- is this necessary? Shouldn't we only bother controlling the // front end here? - $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); + $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); return; } - + # Check permissions $permission_errors = $this->mTitle->getUserPermissionsErrors( 'patrol', $wgUser ); @@ -1554,7 +1641,7 @@ class Article { } #It would be nice to see where the user had actually come from, but for now just guess - $returnto = $rc->mAttribs['rc_type'] == RC_NEW ? 'Newpages' : 'Recentchanges'; + $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges'; $return = Title::makeTitle( NS_SPECIAL, $returnto ); # If it's left up to us, check that the user is allowed to patrol this edit @@ -1575,10 +1662,14 @@ class Article { } } - # Mark the edit as patrolled - RecentChange::markPatrolled( $rcid ); - PatrolLog::record( $rcid ); - wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); + # Check that the revision isn't patrolled already + # Prevents duplicate log entries + if( !$rc->getAttribute( 'rc_patrolled' ) ) { + # Mark the edit as patrolled + RecentChange::markPatrolled( $rcid ); + PatrolLog::record( $rcid ); + wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); + } # Inform the user $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) ); @@ -1724,8 +1815,8 @@ class Article { $updated = Article::flattenRestrictions( $limit ); $changed = ( $current != $updated ); - $changed = $changed || ($this->mTitle->areRestrictionsCascading() != $cascade); - $changed = $changed || ($this->mTitle->mRestrictionsExpiry != $expiry); + $changed = $changed || ($updated && $this->mTitle->areRestrictionsCascading() != $cascade); + $changed = $changed || ($updated && $this->mTitle->mRestrictionsExpiry != $expiry); $protect = ( $updated != '' ); # If nothing's changed, do nothing @@ -1751,12 +1842,16 @@ class Article { } $comment = $wgContLang->ucfirst( wfMsgForContent( $comment_type, $this->mTitle->getPrefixedText() ) ); - foreach( $limit as $action => $restrictions ) { - # Check if the group level required to edit also can protect pages - # Otherwise, people who cannot normally protect can "protect" pages via transclusion - $cascade = ( $cascade && isset($wgGroupPermissions[$restrictions]['protect']) && $wgGroupPermissions[$restrictions]['protect'] ); + # Only restrictions with the 'protect' right can cascade... + # Otherwise, people who cannot normally protect can "protect" pages via transclusion + foreach( $limit as $action => $restriction ) { + # FIXME: can $restriction be an array or what? (same as fixme above) + if( $restriction != 'protect' && $restriction != 'sysop' ) { + $cascade = false; + break; + } } - + $cascade_description = ''; if ($cascade) { $cascade_description = ' ['.wfMsg('protect-summary-cascade').']'; @@ -1770,8 +1865,7 @@ class Article { $comment .= "$expiry_description"; if ( $cascade ) $comment .= "$cascade_description"; - - $rowsAffected = false; + # Update restrictions table foreach( $limit as $action => $restrictions ) { if ($restrictions != '' ) { @@ -1779,18 +1873,11 @@ class Article { array( 'pr_page' => $id, 'pr_type' => $action , 'pr_level' => $restrictions, 'pr_cascade' => $cascade ? 1 : 0 , 'pr_expiry' => $encodedExpiry ), __METHOD__ ); - if($dbw->affectedRows() != 0) - $rowsAffected = true; } else { $dbw->delete( 'page_restrictions', array( 'pr_page' => $id, 'pr_type' => $action ), __METHOD__ ); - if($dbw->affectedRows() != 0) - $rowsAffected = true; } } - if(!$rowsAffected) - // No change - return true; # Insert a null revision $nullRevision = Revision::newNullRevision( $dbw, $id, $comment, true ); @@ -1806,15 +1893,15 @@ class Article { 'page_id' => $id ), 'Article::protect' ); + + wfRunHooks( 'NewRevisionFromEditComplete', array($this, $nullRevision, false) ); wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) ); # Update the protection log $log = new LogPage( 'protect' ); - - - if( $protect ) { - $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason . " [$updated]$cascade_description$expiry_description" ) ); + $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, + trim( $reason . " [$updated]$cascade_description$expiry_description" ) ); } else { $log->addEntry( 'unprotect', $this->mTitle, $reason ); } @@ -1845,7 +1932,7 @@ class Article { } return implode( ':', $bits ); } - + /** * Auto-generates a deletion reason * @param bool &$hasHistory Whether the page has a history @@ -1907,7 +1994,7 @@ class Article { else $reason = wfMsgForContent('excontent', '$1'); } - + // Replace newlines with spaces to prevent uglyness $contents = preg_replace("/[\n\r]/", ' ', $contents); // Calculate the maximum amount of chars to get @@ -1930,18 +2017,20 @@ class Article { $confirm = $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ); - + $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' ); $this->DeleteReason = $wgRequest->getText( 'wpReason' ); - + $reason = $this->DeleteReasonList; - + if ( $reason != 'other' && $this->DeleteReason != '') { // Entry from drop down menu + additional comment $reason .= ': ' . $this->DeleteReason; } elseif ( $reason == 'other' ) { $reason = $this->DeleteReason; } + # Flag to hide all contents of the archived revisions + $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('suppressrevision'); # This code desperately needs to be totally rewritten @@ -1950,7 +2039,7 @@ class Article { $wgOut->readOnlyPage(); return; } - + # Check permissions $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser ); @@ -1980,7 +2069,7 @@ class Article { } if( $confirm ) { - $this->doDelete( $reason ); + $this->doDelete( $reason, $suppress ); if( $wgRequest->getCheck( 'wpWatch' ) ) { $this->doWatch(); } elseif( $this->mTitle->userIsWatching() ) { @@ -2003,10 +2092,10 @@ class Article { array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) ); } } - - return $this->confirmDelete( '', $reason ); + + return $this->confirmDelete( $reason ); } - + /** * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions */ @@ -2018,7 +2107,7 @@ class Article { } return false; } - + /** * @return int approximate revision count */ @@ -2079,10 +2168,9 @@ class Article { /** * Output deletion confirmation dialog - * @param $par string FIXME: do we need this parameter? One Call from Article::delete with '' only. * @param $reason string Prefilled reason */ - function confirmDelete( $par, $reason ) { + function confirmDelete( $reason ) { global $wgOut, $wgUser, $wgContLang; $align = $wgContLang->isRtl() ? 'left' : 'right'; @@ -2092,9 +2180,17 @@ class Article { $wgOut->setRobotpolicy( 'noindex,nofollow' ); $wgOut->addWikiMsg( 'confirmdeletetext' ); - $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=delete' . $par ), 'id' => 'deleteconfirm' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'delete-legend' ) ) . + if( $wgUser->isAllowed( 'suppressrevision' ) ) { + $suppress = "<tr id=\"wpDeleteSuppressRow\" name=\"wpDeleteSuppressRow\"><td></td><td>"; + $suppress .= Xml::checkLabel( wfMsg( 'revdelete-suppress' ), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '2' ) ); + $suppress .= "</td></tr>"; + } else { + $suppress = ''; + } + + $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) . + Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) . + Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) . Xml::openElement( 'table' ) . "<tr id=\"wpDeleteReasonListRow\"> <td align='$align'>" . @@ -2102,7 +2198,7 @@ class Article { "</td> <td>" . Xml::listDropDown( 'wpDeleteReasonList', - wfMsgForContent( 'deletereason-dropdown' ), + wfMsgForContent( 'deletereason-dropdown' ), wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) . "</td> </tr> @@ -2120,6 +2216,7 @@ class Article { Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '3' ) ) . "</td> </tr> + $suppress <tr> <td></td> <td>" . @@ -2131,6 +2228,12 @@ class Article { Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . Xml::closeElement( 'form' ); + if ( $wgUser->isAllowed( 'editinterface' ) ) { + $skin = $wgUser->getSkin(); + $link = $skin->makeLink ( 'MediaWiki:Deletereason-dropdown', wfMsgHtml( 'delete-edit-reasonlist' ) ); + $form .= '<p class="mw-delete-editreasons">' . $link . '</p>'; + } + $wgOut->addHTML( $form ); $this->showLogExtract( $wgOut ); } @@ -2140,25 +2243,24 @@ class Article { * Show relevant lines from the deletion log */ function showLogExtract( $out ) { - $out->addHtml( '<h2>' . htmlspecialchars( LogPage::logName( 'delete' ) ) . '</h2>' ); - $logViewer = new LogViewer( - new LogReader( - new FauxRequest( - array( 'page' => $this->mTitle->getPrefixedText(), - 'type' => 'delete' ) ) ) ); - $logViewer->showList( $out ); + $out->addHtml( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) ); + LogEventsList::showLogExtract( $out, 'delete', $this->mTitle->getPrefixedText() ); } /** * Perform a deletion and output success or failure messages */ - function doDelete( $reason ) { + function doDelete( $reason, $suppress = false ) { global $wgOut, $wgUser; wfDebug( __METHOD__."\n" ); + + $id = $this->getId(); + + $error = ''; - if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) { - if ( $this->doDeleteArticle( $reason ) ) { + if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason, &$error))) { + if ( $this->doDeleteArticle( $reason, $suppress ) ) { $deleted = $this->mTitle->getPrefixedText(); $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) ); @@ -2168,9 +2270,12 @@ class Article { $wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink ); $wgOut->returnToMain( false ); - wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason)); + wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason, $id)); } else { - $wgOut->showFatalError( wfMsg( 'cannotdelete' ) ); + if ($error = '') + $wgOut->showFatalError( wfMsg( 'cannotdelete' ) ); + else + $wgOut->showFatalError( $error ); } } } @@ -2180,7 +2285,7 @@ class Article { * Deletes the article with database consistency, writes logs, purges caches * Returns success */ - function doDeleteArticle( $reason ) { + function doDeleteArticle( $reason, $suppress = false ) { global $wgUseSquid, $wgDeferredUpdateList; global $wgUseTrackbacks; @@ -2198,6 +2303,19 @@ class Article { $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 ); array_push( $wgDeferredUpdateList, $u ); + // Bitfields to further suppress the content + if ( $suppress ) { + $bitfield = 0; + // This should be 15... + $bitfield |= Revision::DELETED_TEXT; + $bitfield |= Revision::DELETED_COMMENT; + $bitfield |= Revision::DELETED_USER; + $bitfield |= Revision::DELETED_RESTRICTED; + } else { + $bitfield = 'rev_deleted'; + } + + $dbw->begin(); // For now, shunt the revision data into the archive table. // Text is *not* removed from the text table; bulk storage // is left intact to avoid breaking block-compression or @@ -2221,8 +2339,9 @@ class Article { 'ar_text_id' => 'rev_text_id', 'ar_text' => '\'\'', // Be explicit to appease 'ar_flags' => '\'\'', // MySQL's "strict mode"... - 'ar_len' => 'rev_len', + 'ar_len' => 'rev_len', 'ar_page_id' => 'page_id', + 'ar_deleted' => $bitfield ), array( 'page_id' => $id, 'page_id = rev_page' @@ -2232,12 +2351,25 @@ class Article { # Delete restrictions for it $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ ); + # Fix category table counts + $cats = array(); + $res = $dbw->select( 'categorylinks', 'cl_to', + array( 'cl_from' => $id ), __METHOD__ ); + foreach( $res as $row ) { + $cats []= $row->cl_to; + } + $this->updateCategoryCounts( array(), $cats ); + # Now that it's safely backed up, delete it $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__); + $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy + if( !$ok ) { + $dbw->rollback(); + return false; + } # If using cascading deletes, we can skip some explicit deletes if ( !$dbw->cascadingDeletes() ) { - $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ ); if ($wgUseTrackbacks) @@ -2257,19 +2389,26 @@ class Article { if ( !$dbw->cleanupTriggers() ) { # Clean up recentchanges entries... - $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), __METHOD__ ); + $dbw->delete( 'recentchanges', + array( 'rc_namespace' => $ns, 'rc_title' => $t, 'rc_type != '.RC_LOG ), + __METHOD__ ); } + $dbw->commit(); # Clear caches Article::onArticleDelete( $this->mTitle ); - # Log the deletion - $log = new LogPage( 'delete' ); - $log->addEntry( 'delete', $this->mTitle, $reason ); - # Clear the cached article id so the interface doesn't act like we exist $this->mTitle->resetArticleID( 0 ); $this->mTitle->mArticleID = 0; + + # Log the deletion, if the page was suppressed, log it at Oversight instead + $logtype = $suppress ? 'suppress' : 'delete'; + $log = new LogPage( $logtype ); + + # Make sure logging got through + $log->addEntry( 'delete', $this->mTitle, $reason, array() ); + return true; } @@ -2280,15 +2419,15 @@ class Article { * performs permissions checks on $wgUser, then calls commitRollback() * to do the dirty work * - * @param string $fromP - Name of the user whose edits to rollback. + * @param string $fromP - Name of the user whose edits to rollback. * @param string $summary - Custom summary. Set to default summary if empty. * @param string $token - Rollback token. * @param bool $bot - If true, mark all reverted edits as bot. - * + * * @param array $resultDetails contains result-specific array of additional values * 'alreadyrolled' : 'current' (rev) * success : 'summary' (str), 'current' (rev), 'target' (rev) - * + * * @return array of errors, each error formatted as * array(messagekey, param1, param2, ...). * On success, the array is empty. This array can also be passed to @@ -2310,10 +2449,10 @@ class Article { # If there were errors, bail out now if(!empty($errors)) return $errors; - + return $this->commitRollback($fromP, $summary, $bot, $resultDetails); } - + /** * Backend implementation of doRollback(), please refer there for parameter * and return value documentation @@ -2322,9 +2461,9 @@ class Article { * rollback to the DB Therefore, you should only call this function direct- * ly if you want to use custom permissions checks. If you don't, use * doRollback() instead. - */ + */ public function commitRollback($fromP, $summary, $bot, &$resultDetails) { - global $wgUseRCPatrol, $wgUser; + global $wgUseRCPatrol, $wgUser, $wgLang; $dbw = wfGetDB( DB_MASTER ); if( wfReadOnly() ) { @@ -2352,7 +2491,7 @@ class Article { $user = intval( $current->getUser() ); $user_text = $dbw->addQuotes( $current->getUserText() ); $s = $dbw->selectRow( 'revision', - array( 'rev_id', 'rev_timestamp' ), + array( 'rev_id', 'rev_timestamp', 'rev_deleted' ), array( 'rev_page' => $current->getPage(), "rev_user <> {$user} OR rev_user_text <> {$user_text}" ), __METHOD__, @@ -2362,8 +2501,11 @@ class Article { if( $s === false ) { # No one else ever edited this page return array(array('cantrollback')); + } else if( $s->rev_deleted & REVISION::DELETED_TEXT || $s->rev_deleted & REVISION::DELETED_USER ) { + # Only admins can see this text + return array(array('notvisiblerev')); } - + $set = array(); if ( $bot && $wgUser->isAllowed('markbotedits') ) { # Mark all reverted edits as bot @@ -2386,15 +2528,17 @@ class Article { # Generate the edit summary if necessary $target = Revision::newFromId( $s->rev_id ); - if( empty( $summary ) ) - { - global $wgLang; - $summary = wfMsgForContent( 'revertpage', - $target->getUserText(), $from, - $s->rev_id, $wgLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp), true), - $current->getId(), $wgLang->timeanddate($current->getTimestamp()) - ); + if( empty( $summary ) ){ + $summary = wfMsgForContent( 'revertpage' ); } + + # Allow the custom summary to use the same args as the default message + $args = array( + $target->getUserText(), $from, $s->rev_id, + $wgLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp), true), + $current->getId(), $wgLang->timeanddate($current->getTimestamp()) + ); + $summary = wfMsgReplaceArgs( $summary, $args ); # Save $flags = EDIT_UPDATE; @@ -2404,7 +2548,7 @@ class Article { if( $bot && ($wgUser->isAllowed('markbotedits') || $wgUser->isAllowed('bot')) ) $flags |= EDIT_FORCE_BOT; - $this->doEdit( $target->getText(), $summary, $flags ); + $this->doEdit( $target->getText(), $summary, $flags, $target->getId() ); wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target ) ); @@ -2439,6 +2583,19 @@ class Article { $wgOut->rateLimited(); return; } + if( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ){ + $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) ); + $errArray = $result[0]; + $errMsg = array_shift( $errArray ); + $wgOut->addWikiMsgArray( $errMsg, $errArray ); + if( isset( $details['current'] ) ){ + $current = $details['current']; + if( $current->getComment() != '' ) { + $wgOut->addWikiMsgArray( 'editcomment', array( $wgUser->getSkin()->formatComment( $current->getComment() ) ), array( 'replaceafter' ) ); + } + } + return; + } # Display permissions errors before read-only message -- there's no # point in misleading the user into thinking the inability to rollback # is only temporary. @@ -2469,6 +2626,11 @@ class Article { . $wgUser->getSkin()->userToolLinks( $target->getUser(), $target->getUserText() ); $wgOut->addHtml( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) ); $wgOut->returnToMain( false, $this->mTitle ); + + if( !$wgRequest->getBool( 'hidediff', false ) ) { + $de = new DifferenceEngine( $this->mTitle, $current->getId(), 'next', false, true ); + $de->showDiff( '', '' ); + } } @@ -2477,11 +2639,12 @@ class Article { * @private */ function viewUpdates() { - global $wgDeferredUpdateList; + global $wgDeferredUpdateList, $wgUser; if ( 0 != $this->getID() ) { + # Don't update page view counters on views from bot users (bug 14044) global $wgDisableCounters; - if( !$wgDisableCounters ) { + if( !$wgDisableCounters && !$wgUser->isAllowed( 'bot' ) ) { Article::incViewCount( $this->getID() ); $u = new SiteStatsUpdate( 1, 0, 0 ); array_push( $wgDeferredUpdateList, $u ); @@ -2489,7 +2652,6 @@ class Article { } # Update newtalk / watchlist notification status - global $wgUser; $wgUser->clearNotification( $this->mTitle ); } @@ -2546,7 +2708,7 @@ class Article { # Save it to the parser cache if ( $wgEnableParserCache ) { - $parserCache =& ParserCache::singleton(); + $parserCache = ParserCache::singleton(); $parserCache->save( $editInfo->output, $this, $wgUser ); } @@ -2646,7 +2808,7 @@ class Article { $sk = $wgUser->getSkin(); $lnk = $current ? wfMsg( 'currentrevisionlink' ) - : $lnk = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) ); + : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) ); $curdiff = $current ? wfMsg( 'diff' ) : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=cur&oldid='.$oldid ); @@ -2664,16 +2826,38 @@ class Article { ? wfMsg( 'diff' ) : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid ); - $userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() ) - . $sk->userToolLinks( $revision->getUser(), $revision->getUserText() ); + $cdel=''; + if( $wgUser->isAllowed( 'deleterevision' ) ) { + $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); + if( $revision->isCurrent() ) { + // We don't handle top deleted edits too well + $cdel = wfMsgHtml('rev-delundel'); + } else if( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) { + // If revision was hidden from sysops + $cdel = wfMsgHtml('rev-delundel'); + } else { + $cdel = $sk->makeKnownLinkObj( $revdel, + wfMsgHtml('rev-delundel'), + 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) . + '&oldid=' . urlencode( $oldid ) ); + // Bolden oversighted content + if( $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) + $cdel = "<strong>$cdel</strong>"; + } + $cdel = "(<small>$cdel</small>) "; + } + # Show user links if allowed to see them. Normally they + # are hidden regardless, but since we can already see the text here... + $userlinks = $sk->revUserTools( $revision, false ); $m = wfMsg( 'revision-info-current' ); $infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-' ? 'revision-info-current' : 'revision-info'; - + $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsg( $infomsg, $td, $userlinks ) . "</div>\n" . - "\n\t\t\t\t<div id=\"mw-revision-nav\">" . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t"; + + "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t"; $wgOut->setSubtitle( $r ); } @@ -2731,9 +2915,9 @@ class Article { $printable = $wgRequest->getVal( 'printable' ); $page = $wgRequest->getVal( 'page' ); - //check for non-standard user language; this covers uselang, + //check for non-standard user language; this covers uselang, //and extensions for auto-detecting user language. - $ulang = $wgLang->getCode(); + $ulang = $wgLang->getCode(); $clang = $wgContLang->getCode(); $cacheable = $wgUseFileCache @@ -2814,6 +2998,8 @@ class Article { $revision->insertOn( $dbw ); $this->updateRevisionOn( $dbw, $revision ); $dbw->commit(); + + wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, false) ); wfProfileOut( __METHOD__ ); } @@ -2911,6 +3097,15 @@ class Article { static function onArticleDelete( $title ) { global $wgUseFileCache, $wgMessageCache; + // Update existence markers on article/talk tabs... + if( $title->isTalkPage() ) { + $other = $title->getSubjectPage(); + } else { + $other = $title->getTalkPage(); + } + $other->invalidateCache(); + $other->purgeSquid(); + $title->touchLinks(); $title->purgeSquid(); @@ -2920,13 +3115,20 @@ class Article { @unlink( $cm->fileCacheName() ); } - if( $title->getNamespace() == NS_MEDIAWIKI) { + # Messages + if( $title->getNamespace() == NS_MEDIAWIKI ) { $wgMessageCache->replace( $title->getDBkey(), false ); } + # Images if( $title->getNamespace() == NS_IMAGE ) { $update = new HTMLCacheUpdate( $title, 'imagelinks' ); $update->doUpdate(); } + # User talk pages + if( $title->getNamespace() == NS_USER_TALK ) { + $user = User::newFromName( $title->getText(), false ); + $user->setNewtalk( false ); + } } /** @@ -2940,7 +3142,7 @@ class Article { // Invalidate the caches of all pages which redirect here $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' ); - + # Purge squid for this page only $title->purgeSquid(); @@ -2954,6 +3156,15 @@ class Article { /**#@-*/ /** + * Overriden by ImagePage class, only present here to avoid a fatal error + * Called for ?action=revert + */ + public function revert(){ + global $wgOut; + $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' ); + } + + /** * Info about this page * Called for ?action=info when $wgAllowPageInfo is on. * @@ -3080,38 +3291,33 @@ class Article { } /** - * Return an auto-generated summary if the text provided is a redirect. + * Returns a list of hidden categories this page is a member of. + * Uses the page_props and categorylinks tables. * - * @param string $text The wikitext to check - * @return string '' or an appropriate summary + * @return array Array of Title objects */ - public static function getRedirectAutosummary( $text ) { - $rt = Title::newFromRedirect( $text ); - if( is_object( $rt ) ) - return wfMsgForContent( 'autoredircomment', $rt->getFullText() ); - else - return ''; - } + function getHiddenCategories() { + $result = array(); + $id = $this->mTitle->getArticleID(); + if( $id == 0 ) { + return array(); + } - /** - * Return an auto-generated summary if the new text is much shorter than - * the old text. - * - * @param string $oldtext The previous text of the page - * @param string $text The submitted text of the page - * @return string An appropriate autosummary, or an empty string. - */ - public static function getBlankingAutosummary( $oldtext, $text ) { - if ($oldtext!='' && $text=='') { - return wfMsgForContent('autosumm-blank'); - } elseif (strlen($oldtext) > 10 * strlen($text) && strlen($text) < 500) { - #Removing more than 90% of the article - global $wgContLang; - $truncatedtext = $wgContLang->truncate($text, max(0, 200 - strlen(wfMsgForContent('autosumm-replace'))), '...'); - return wfMsgForContent('autosumm-replace', $truncatedtext); - } else { - return ''; + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ), + array( 'cl_to' ), + array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat', + 'page_namespace' => NS_CATEGORY, 'page_title=cl_to'), + 'Article:getHiddenCategories' ); + if ( false !== $res ) { + if ( $dbr->numRows( $res ) ) { + while ( $row = $dbr->fetchObject( $res ) ) { + $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to ); + } + } } + $dbr->freeResult( $res ); + return $result; } /** @@ -3122,38 +3328,42 @@ class Article { * @return string An appropriate autosummary, or an empty string. */ public static function getAutosummary( $oldtext, $newtext, $flags ) { + # Decide what kind of autosummary is needed. - # This code is UGLY UGLY UGLY. - # Somebody PLEASE come up with a more elegant way to do it. - - #Redirect autosummaries - $summary = self::getRedirectAutosummary( $newtext ); - - if ($summary) - return $summary; - - #Blanking autosummaries - if (!($flags & EDIT_NEW)) - $summary = self::getBlankingAutosummary( $oldtext, $newtext ); - - if ($summary) - return $summary; + # Redirect autosummaries + $rt = Title::newFromRedirect( $newtext ); + if( is_object( $rt ) ) { + return wfMsgForContent( 'autoredircomment', $rt->getFullText() ); + } - #New page autosummaries - if ($flags & EDIT_NEW && strlen($newtext)) { - #If they're making a new article, give its text, truncated, in the summary. + # New page autosummaries + if( $flags & EDIT_NEW && strlen( $newtext ) ) { + # If they're making a new article, give its text, truncated, in the summary. global $wgContLang; $truncatedtext = $wgContLang->truncate( str_replace("\n", ' ', $newtext), max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new') ) ), '...' ); - $summary = wfMsgForContent( 'autosumm-new', $truncatedtext ); + return wfMsgForContent( 'autosumm-new', $truncatedtext ); } - if ($summary) - return $summary; + # Blanking autosummaries + if( $oldtext != '' && $newtext == '' ) { + return wfMsgForContent('autosumm-blank'); + } elseif( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500) { + # Removing more than 90% of the article + global $wgContLang; + $truncatedtext = $wgContLang->truncate( + $newtext, + max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ), + '...' + ); + return wfMsgForContent( 'autosumm-replace', $truncatedtext ); + } - return $summary; + # If we reach this point, there's no applicable autosummary for our case, so our + # autosummary is empty. + return ''; } /** @@ -3175,7 +3385,7 @@ class Article { $popts->setTidy(false); $popts->enableLimitReport( false ); if ( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) { - $parserCache =& ParserCache::singleton(); + $parserCache = ParserCache::singleton(); $parserCache->save( $parserOutput, $this, $wgUser ); } @@ -3235,4 +3445,60 @@ class Article { $wgOut->addParserOutput( $parserOutput ); } + /** + * Update all the appropriate counts in the category table, given that + * we've added the categories $added and deleted the categories $deleted. + * + * @param $added array The names of categories that were added + * @param $deleted array The names of categories that were deleted + * @return null + */ + public function updateCategoryCounts( $added, $deleted ) { + $ns = $this->mTitle->getNamespace(); + $dbw = wfGetDB( DB_MASTER ); + + # First make sure the rows exist. If one of the "deleted" ones didn't + # exist, we might legitimately not create it, but it's simpler to just + # create it and then give it a negative value, since the value is bogus + # anyway. + # + # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE. + $insertCats = array_merge( $added, $deleted ); + if( !$insertCats ) { + # Okay, nothing to do + return; + } + $insertRows = array(); + foreach( $insertCats as $cat ) { + $insertRows[] = array( 'cat_title' => $cat ); + } + $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' ); + + $addFields = array( 'cat_pages = cat_pages + 1' ); + $removeFields = array( 'cat_pages = cat_pages - 1' ); + if( $ns == NS_CATEGORY ) { + $addFields[] = 'cat_subcats = cat_subcats + 1'; + $removeFields[] = 'cat_subcats = cat_subcats - 1'; + } elseif( $ns == NS_IMAGE ) { + $addFields[] = 'cat_files = cat_files + 1'; + $removeFields[] = 'cat_files = cat_files - 1'; + } + + if ( $added ) { + $dbw->update( + 'category', + $addFields, + array( 'cat_title' => $added ), + __METHOD__ + ); + } + if ( $deleted ) { + $dbw->update( + 'category', + $removeFields, + array( 'cat_title' => $deleted ), + __METHOD__ + ); + } + } } |