}
}
+ /**
+ * Loads page_touched and returns a value indicating if it should be used
+ * @return boolean true if not a redirect
+ */
+ public function checkTouched() {
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return !$this->mIsRedirect;
+ }
+
+ /**
+ * Get the page_touched field
+ * @return string containing GMT timestamp
+ */
+ public function getTouched() {
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return $this->mTouched;
+ }
+
+ /**
+ * Get the page_latest field
+ * @return integer rev_id of current revision
+ */
+ public function getLatest() {
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return (int)$this->mLatest;
+ }
+
/**
* Loads everything except the text
* This isn't necessary for all uses, so it's only done if needed.
}
}
+ /**
+ * Get the cached timestamp for the last time the page changed.
+ * This is only used to help handle slave lag by comparing to page_touched.
+ * @return string MW timestamp
+ */
+ protected function getCachedLastEditTime() {
+ global $wgMemc;
+ $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
+ return $wgMemc->get( $key );
+ }
+
+ /**
+ * Set the cached timestamp for the last time the page changed.
+ * This is only used to help handle slave lag by comparing to page_touched.
+ * @param $timestamp string
+ * @return void
+ */
+ public function setCachedLastEditTime( $timestamp ) {
+ global $wgMemc;
+ $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
+ $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60*15 );
+ }
+
/**
* Get a list of users who have edited this article, not including the user who made
* the most recent revision, which you can get from $article->getUser() if you want it
return new UserArrayFromResult( $res );
}
+ /**
+ * Get the last N authors
+ * @param $num Integer: number of revisions to get
+ * @param $revLatest String: the latest rev_id, selected from the master (optional)
+ * @return array Array of authors, duplicates not removed
+ */
+ public function getLastNAuthors( $num, $revLatest = 0 ) {
+ wfProfileIn( __METHOD__ );
+ // First try the slave
+ // If that doesn't have the latest revision, try the master
+ $continue = 2;
+ $db = wfGetDB( DB_SLAVE );
+
+ do {
+ $res = $db->select( array( 'page', 'revision' ),
+ array( 'rev_id', 'rev_user_text' ),
+ array(
+ 'page_namespace' => $this->mTitle->getNamespace(),
+ 'page_title' => $this->mTitle->getDBkey(),
+ 'rev_page = page_id'
+ ), __METHOD__,
+ array(
+ 'ORDER BY' => 'rev_timestamp DESC',
+ 'LIMIT' => $num
+ )
+ );
+
+ if ( !$res ) {
+ wfProfileOut( __METHOD__ );
+ return array();
+ }
+
+ $row = $db->fetchObject( $res );
+
+ if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
+ $db = wfGetDB( DB_MASTER );
+ $continue--;
+ } else {
+ $continue = 0;
+ }
+ } while ( $continue );
+
+ $authors = array( $row->rev_user_text );
+
+ foreach ( $res as $row ) {
+ $authors[] = $row->rev_user_text;
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $authors;
+ }
+
/**
* Should the parser cache be used?
*
return $pool->getParserOutput();
}
+ /**
+ * Do standard deferred updates after page view
+ * @param $user User The relevant user
+ */
+ public function doViewUpdates( User $user ) {
+ global $wgDisableCounters;
+ if ( wfReadOnly() ) {
+ return;
+ }
+
+ # Don't update page view counters on views from bot users (bug 14044)
+ if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->mTitle->exists() ) {
+ DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
+ }
+
+ # Update newtalk / watchlist notification status
+ $user->clearNotification( $this->mTitle );
+ }
+
/**
* Perform the actions of a page purging
*/
return $result;
}
- /**
- * Get the cached timestamp for the last time the page changed.
- * This is only used to help handle slave lag by comparing to page_touched.
- * @return string MW timestamp
- */
- protected function getCachedLastEditTime() {
- global $wgMemc;
- $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
- return $wgMemc->get( $key );
- }
-
- /**
- * Set the cached timestamp for the last time the page changed.
- * This is only used to help handle slave lag by comparing to page_touched.
- * @param $timestamp string
- * @return void
- */
- public function setCachedLastEditTime( $timestamp ) {
- global $wgMemc;
- $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
- $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60*15 );
- }
-
/**
* Add row to the redirect table if this is a redirect, remove otherwise.
*
}
/**
- * Update the article's restriction field, and leave a log entry.
- * This works for protection both existing and non-existing pages.
- *
- * @param $limit Array: set of restriction keys
- * @param $reason String
- * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
- * @param $expiry Array: per restriction type expiration
- * @param $user User The user updating the restrictions
- * @return bool true on success
+ * Get parser options suitable for rendering the primary article wikitext
+ * @param User|string $user User object or 'canonical'
+ * @return ParserOptions
*/
- public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
+ public function makeParserOptions( $user ) {
global $wgContLang;
-
- if ( wfReadOnly() ) {
- return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
+ if ( $user instanceof User ) { // settings per user (even anons)
+ $options = ParserOptions::newFromUser( $user );
+ } else { // canonical settings
+ $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
}
+ $options->enableLimitReport(); // show inclusion/loop reports
+ $options->setTidy( true ); // fix bad HTML
+ return $options;
+ }
- $restrictionTypes = $this->mTitle->getRestrictionTypes();
+ /**
+ * Prepare text which is about to be saved.
+ * Returns a stdclass with source, pst and output members
+ */
+ public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+ global $wgParser, $wgContLang, $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
+ // @TODO fixme: check $user->getId() here???
+ if ( $this->mPreparedEdit
+ && $this->mPreparedEdit->newText == $text
+ && $this->mPreparedEdit->revid == $revid
+ ) {
+ // Already prepared
+ return $this->mPreparedEdit;
+ }
- $id = $this->mTitle->getArticleID();
+ $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
+ wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
- if ( !$cascade ) {
- $cascade = false;
- }
+ $edit = (object)array();
+ $edit->revid = $revid;
+ $edit->newText = $text;
+ $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+ $edit->popts = $this->makeParserOptions( 'canonical' );
+ $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
+ $edit->oldText = $this->getRawText();
- // Take this opportunity to purge out expired restrictions
- Title::purgeExpiredRestrictions();
+ $this->mPreparedEdit = $edit;
- # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
- # we expect a single selection, but the schema allows otherwise.
- $isProtected = false;
- $protect = false;
- $changed = false;
+ return $edit;
+ }
- $dbw = wfGetDB( DB_MASTER );
+ /**
+ * Do standard deferred updates after page edit.
+ * Update links tables, site stats, search index and message cache.
+ * Purges pages that include this page if the text was changed here.
+ * Every 100th edit, prune the recent changes table.
+ *
+ * @private
+ * @param $revision Revision object
+ * @param $user User object that did the revision
+ * @param $options Array of options, following indexes are used:
+ * - changed: boolean, whether the revision changed the content (default true)
+ * - created: boolean, whether the revision created the page (default false)
+ * - oldcountable: boolean or null (default null):
+ * - boolean: whether the page was counted as an article before that
+ * revision, only used in changed is true and created is false
+ * - null: don't change the article count
+ */
+ public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
+ global $wgEnableParserCache;
- foreach ( $restrictionTypes as $action ) {
- if ( !isset( $expiry[$action] ) ) {
- $expiry[$action] = $dbw->getInfinity();
- }
- if ( !isset( $limit[$action] ) ) {
- $limit[$action] = '';
- } elseif ( $limit[$action] != '' ) {
- $protect = true;
- }
+ wfProfileIn( __METHOD__ );
- # Get current restrictions on $action
- $current = implode( '', $this->mTitle->getRestrictions( $action ) );
- if ( $current != '' ) {
- $isProtected = true;
- }
+ $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
+ $text = $revision->getText();
- if ( $limit[$action] != $current ) {
- $changed = true;
- } elseif ( $limit[$action] != '' ) {
- # Only check expiry change if the action is actually being
- # protected, since expiry does nothing on an not-protected
- # action.
- if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
+ # Parse the text
+ # Be careful not to double-PST: $text is usually already PST-ed once
+ if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
+ wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
+ $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
+ } else {
+ wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
+ $editInfo = $this->mPreparedEdit;
+ }
+
+ # Save it to the parser cache
+ if ( $wgEnableParserCache ) {
+ $parserCache = ParserCache::singleton();
+ $parserCache->save( $editInfo->output, $this, $editInfo->popts );
+ }
+
+ # Update the links tables
+ $u = new LinksUpdate( $this->mTitle, $editInfo->output );
+ $u->doUpdate();
+
+ wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
+
+ if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
+ if ( 0 == mt_rand( 0, 99 ) ) {
+ // Flush old entries from the `recentchanges` table; we do this on
+ // random requests so as to avoid an increase in writes for no good reason
+ global $wgRCMaxAge;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
+ $dbw->delete(
+ 'recentchanges',
+ array( "rc_timestamp < '$cutoff'" ),
+ __METHOD__
+ );
+ }
+ }
+
+ if ( !$this->mTitle->exists() ) {
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ $id = $this->getId();
+ $title = $this->mTitle->getPrefixedDBkey();
+ $shortTitle = $this->mTitle->getDBkey();
+
+ if ( !$options['changed'] ) {
+ $good = 0;
+ $total = 0;
+ } elseif ( $options['created'] ) {
+ $good = (int)$this->isCountable( $editInfo );
+ $total = 1;
+ } elseif ( $options['oldcountable'] !== null ) {
+ $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
+ $total = 0;
+ } else {
+ $good = 0;
+ $total = 0;
+ }
+
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
+
+ # If this is another user's talk page, update newtalk.
+ # Don't do this if $options['changed'] = false (null-edits) nor if
+ # it's a minor edit and the user doesn't want notifications for those.
+ if ( $options['changed']
+ && $this->mTitle->getNamespace() == NS_USER_TALK
+ && $shortTitle != $user->getTitleKey()
+ && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
+ ) {
+ if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
+ $other = User::newFromName( $shortTitle, false );
+ if ( !$other ) {
+ wfDebug( __METHOD__ . ": invalid username\n" );
+ } elseif ( User::isIP( $shortTitle ) ) {
+ // An anonymous user
+ $other->setNewtalk( true );
+ } elseif ( $other->isLoggedIn() ) {
+ $other->setNewtalk( true );
+ } else {
+ wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
+ }
+ }
+ }
+
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ MessageCache::singleton()->replace( $shortTitle, $text );
+ }
+
+ if( $options['created'] ) {
+ self::onArticleCreate( $this->mTitle );
+ } else {
+ self::onArticleEdit( $this->mTitle );
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Edit an article without doing all that other stuff
+ * The article must already exist; link tables etc
+ * are not updated, caches are not flushed.
+ *
+ * @param $text String: text submitted
+ * @param $user User The relevant user
+ * @param $comment String: comment submitted
+ * @param $minor Boolean: whereas it's a minor modification
+ */
+ public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ wfProfileIn( __METHOD__ );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $revision = new Revision( array(
+ 'page' => $this->getId(),
+ 'text' => $text,
+ 'comment' => $comment,
+ 'minor_edit' => $minor ? 1 : 0,
+ ) );
+ $revision->insertOn( $dbw );
+ $this->updateRevisionOn( $dbw, $revision );
+
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Update the article's restriction field, and leave a log entry.
+ * This works for protection both existing and non-existing pages.
+ *
+ * @param $limit Array: set of restriction keys
+ * @param $reason String
+ * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
+ * @param $expiry Array: per restriction type expiration
+ * @param $user User The user updating the restrictions
+ * @return bool true on success
+ */
+ public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
+ global $wgContLang;
+
+ if ( wfReadOnly() ) {
+ return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
+ }
+
+ $restrictionTypes = $this->mTitle->getRestrictionTypes();
+
+ $id = $this->mTitle->getArticleID();
+
+ if ( !$cascade ) {
+ $cascade = false;
+ }
+
+ // Take this opportunity to purge out expired restrictions
+ Title::purgeExpiredRestrictions();
+
+ # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
+ # we expect a single selection, but the schema allows otherwise.
+ $isProtected = false;
+ $protect = false;
+ $changed = false;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ foreach ( $restrictionTypes as $action ) {
+ if ( !isset( $expiry[$action] ) ) {
+ $expiry[$action] = $dbw->getInfinity();
+ }
+ if ( !isset( $limit[$action] ) ) {
+ $limit[$action] = '';
+ } elseif ( $limit[$action] != '' ) {
+ $protect = true;
+ }
+
+ # Get current restrictions on $action
+ $current = implode( '', $this->mTitle->getRestrictions( $action ) );
+ if ( $current != '' ) {
+ $isProtected = true;
+ }
+
+ if ( $limit[$action] != $current ) {
+ $changed = true;
+ } elseif ( $limit[$action] != '' ) {
+ # Only check expiry change if the action is actually being
+ # protected, since expiry does nothing on an not-protected
+ # action.
+ if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
$changed = true;
}
}
}
/**
- * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
- *
- * @deprecated in 1.19; use Title::isBigDeletion() instead.
- * @return bool
- */
- public function isBigDeletion() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->mTitle->isBigDeletion();
- }
-
- /**
- * Get the approximate revision count of this page.
+ * Back-end article deletion
+ * Deletes the article with database consistency, writes logs, purges caches
*
- * @deprecated in 1.19; use Title::estimateRevisionCount() instead.
- * @return int
+ * @param $reason string delete reason for deletion log
+ * @param $suppress bitfield
+ * Revision::DELETED_TEXT
+ * Revision::DELETED_COMMENT
+ * Revision::DELETED_USER
+ * Revision::DELETED_RESTRICTED
+ * @param $id int article ID
+ * @param $commit boolean defaults to true, triggers transaction end
+ * @param &$errors Array of errors to append to
+ * @param $user User The relevant user
+ * @return boolean true if successful
*/
- public function estimateRevisionCount() {
- wfDeprecated( __METHOD__, '1.19' );
- return $this->mTitle->estimateRevisionCount();
- }
+ public function doDeleteArticle(
+ $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+ ) {
+ global $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
- /**
- * Get the last N authors
- * @param $num Integer: number of revisions to get
- * @param $revLatest String: the latest rev_id, selected from the master (optional)
- * @return array Array of authors, duplicates not removed
- */
- public function getLastNAuthors( $num, $revLatest = 0 ) {
- wfProfileIn( __METHOD__ );
- // First try the slave
- // If that doesn't have the latest revision, try the master
- $continue = 2;
- $db = wfGetDB( DB_SLAVE );
+ wfDebug( __METHOD__ . "\n" );
- do {
- $res = $db->select( array( 'page', 'revision' ),
- array( 'rev_id', 'rev_user_text' ),
- array(
- 'page_namespace' => $this->mTitle->getNamespace(),
- 'page_title' => $this->mTitle->getDBkey(),
- 'rev_page = page_id'
- ), __METHOD__,
- array(
- 'ORDER BY' => 'rev_timestamp DESC',
- 'LIMIT' => $num
- )
- );
-
- if ( !$res ) {
- wfProfileOut( __METHOD__ );
- return array();
- }
-
- $row = $db->fetchObject( $res );
-
- if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
- $db = wfGetDB( DB_MASTER );
- $continue--;
- } else {
- $continue = 0;
- }
- } while ( $continue );
-
- $authors = array( $row->rev_user_text );
-
- foreach ( $res as $row ) {
- $authors[] = $row->rev_user_text;
- }
-
- wfProfileOut( __METHOD__ );
- return $authors;
- }
-
- /**
- * Back-end article deletion
- * Deletes the article with database consistency, writes logs, purges caches
- *
- * @param $reason string delete reason for deletion log
- * @param $suppress bitfield
- * Revision::DELETED_TEXT
- * Revision::DELETED_COMMENT
- * Revision::DELETED_USER
- * Revision::DELETED_RESTRICTED
- * @param $id int article ID
- * @param $commit boolean defaults to true, triggers transaction end
- * @param &$errors Array of errors to append to
- * @param $user User The relevant user
- * @return boolean true if successful
- */
- public function doDeleteArticle(
- $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
- ) {
- global $wgUser;
- $user = is_null( $user ) ? $wgUser : $user;
-
- wfDebug( __METHOD__ . "\n" );
-
- if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
- return false;
- }
- $dbw = wfGetDB( DB_MASTER );
- $t = $this->mTitle->getDBkey();
- $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
+ if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
+ return false;
+ }
+ $dbw = wfGetDB( DB_MASTER );
+ $t = $this->mTitle->getDBkey();
+ $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
if ( $t === '' || $id == 0 ) {
return false;
return array( array( 'notanarticle' ) );
}
- $from = str_replace( '_', ' ', $fromP );
- # User name given should match up with the top revision.
- # If the user was deleted then $from should be empty.
- if ( $from != $current->getUserText() ) {
- $resultDetails = array( 'current' => $current );
- return array( array( 'alreadyrolled',
- htmlspecialchars( $this->mTitle->getPrefixedText() ),
- htmlspecialchars( $fromP ),
- htmlspecialchars( $current->getUserText() )
- ) );
- }
-
- # Get the last edit not by this guy...
- # Note: these may not be public values
- $user = intval( $current->getRawUser() );
- $user_text = $dbw->addQuotes( $current->getRawUserText() );
- $s = $dbw->selectRow( 'revision',
- array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
- array( 'rev_page' => $current->getPage(),
- "rev_user != {$user} OR rev_user_text != {$user_text}"
- ), __METHOD__,
- array( 'USE INDEX' => 'page_timestamp',
- 'ORDER BY' => 'rev_timestamp DESC' )
- );
- if ( $s === false ) {
- # No one else ever edited this page
- return array( array( 'cantrollback' ) );
- } elseif ( $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 && $guser->isAllowed( 'markbotedits' ) ) {
- # Mark all reverted edits as bot
- $set['rc_bot'] = 1;
- }
-
- if ( $wgUseRCPatrol ) {
- # Mark all reverted edits as patrolled
- $set['rc_patrolled'] = 1;
- }
-
- if ( count( $set ) ) {
- $dbw->update( 'recentchanges', $set,
- array( /* WHERE */
- 'rc_cur_id' => $current->getPage(),
- 'rc_user_text' => $current->getUserText(),
- "rc_timestamp > '{$s->rev_timestamp}'",
- ), __METHOD__
- );
- }
-
- # Generate the edit summary if necessary
- $target = Revision::newFromId( $s->rev_id );
- if ( empty( $summary ) ) {
- if ( $from == '' ) { // no public user name
- $summary = wfMsgForContent( 'revertpage-nouser' );
- } else {
- $summary = wfMsgForContent( 'revertpage' );
- }
- }
-
- # Allow the custom summary to use the same args as the default message
- $args = array(
- $target->getUserText(), $from, $s->rev_id,
- $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
- $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
- );
- $summary = wfMsgReplaceArgs( $summary, $args );
-
- # Save
- $flags = EDIT_UPDATE;
-
- if ( $guser->isAllowed( 'minoredit' ) ) {
- $flags |= EDIT_MINOR;
- }
-
- if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
- $flags |= EDIT_FORCE_BOT;
- }
-
- # Actually store the edit
- $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
- if ( !empty( $status->value['revision'] ) ) {
- $revId = $status->value['revision']->getId();
- } else {
- $revId = false;
- }
-
- wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
-
- $resultDetails = array(
- 'summary' => $summary,
- 'current' => $current,
- 'target' => $target,
- 'newid' => $revId
- );
-
- return array();
- }
-
- /**
- * Do standard deferred updates after page view
- * @param $user User The relevant user
- */
- public function doViewUpdates( User $user ) {
- global $wgDisableCounters;
- if ( wfReadOnly() ) {
- return;
- }
-
- # Don't update page view counters on views from bot users (bug 14044)
- if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->mTitle->exists() ) {
- DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
- DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
- }
-
- # Update newtalk / watchlist notification status
- $user->clearNotification( $this->mTitle );
- }
-
- /**
- * Prepare text which is about to be saved.
- * Returns a stdclass with source, pst and output members
- */
- public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
- global $wgParser, $wgContLang, $wgUser;
- $user = is_null( $user ) ? $wgUser : $user;
- // @TODO fixme: check $user->getId() here???
- if ( $this->mPreparedEdit
- && $this->mPreparedEdit->newText == $text
- && $this->mPreparedEdit->revid == $revid
- ) {
- // Already prepared
- return $this->mPreparedEdit;
- }
-
- $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
- wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
-
- $edit = (object)array();
- $edit->revid = $revid;
- $edit->newText = $text;
- $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
- $edit->popts = $this->makeParserOptions( 'canonical' );
- $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
- $edit->oldText = $this->getRawText();
-
- $this->mPreparedEdit = $edit;
-
- return $edit;
- }
-
- /**
- * Do standard deferred updates after page edit.
- * Update links tables, site stats, search index and message cache.
- * Purges pages that include this page if the text was changed here.
- * Every 100th edit, prune the recent changes table.
- *
- * @private
- * @param $revision Revision object
- * @param $user User object that did the revision
- * @param $options Array of options, following indexes are used:
- * - changed: boolean, whether the revision changed the content (default true)
- * - created: boolean, whether the revision created the page (default false)
- * - oldcountable: boolean or null (default null):
- * - boolean: whether the page was counted as an article before that
- * revision, only used in changed is true and created is false
- * - null: don't change the article count
- */
- public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
- global $wgEnableParserCache;
-
- wfProfileIn( __METHOD__ );
-
- $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
- $text = $revision->getText();
-
- # Parse the text
- # Be careful not to double-PST: $text is usually already PST-ed once
- if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
- wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
- $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
- } else {
- wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
- $editInfo = $this->mPreparedEdit;
- }
-
- # Save it to the parser cache
- if ( $wgEnableParserCache ) {
- $parserCache = ParserCache::singleton();
- $parserCache->save( $editInfo->output, $this, $editInfo->popts );
- }
-
- # Update the links tables
- $u = new LinksUpdate( $this->mTitle, $editInfo->output );
- $u->doUpdate();
-
- wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
-
- if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
- if ( 0 == mt_rand( 0, 99 ) ) {
- // Flush old entries from the `recentchanges` table; we do this on
- // random requests so as to avoid an increase in writes for no good reason
- global $wgRCMaxAge;
-
- $dbw = wfGetDB( DB_MASTER );
- $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
- $dbw->delete(
- 'recentchanges',
- array( "rc_timestamp < '$cutoff'" ),
- __METHOD__
- );
- }
- }
-
- if ( !$this->mTitle->exists() ) {
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $id = $this->getId();
- $title = $this->mTitle->getPrefixedDBkey();
- $shortTitle = $this->mTitle->getDBkey();
-
- if ( !$options['changed'] ) {
- $good = 0;
- $total = 0;
- } elseif ( $options['created'] ) {
- $good = (int)$this->isCountable( $editInfo );
- $total = 1;
- } elseif ( $options['oldcountable'] !== null ) {
- $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
- $total = 0;
- } else {
- $good = 0;
- $total = 0;
- }
-
- DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
- DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
-
- # If this is another user's talk page, update newtalk.
- # Don't do this if $options['changed'] = false (null-edits) nor if
- # it's a minor edit and the user doesn't want notifications for those.
- if ( $options['changed']
- && $this->mTitle->getNamespace() == NS_USER_TALK
- && $shortTitle != $user->getTitleKey()
- && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
- ) {
- if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
- $other = User::newFromName( $shortTitle, false );
- if ( !$other ) {
- wfDebug( __METHOD__ . ": invalid username\n" );
- } elseif ( User::isIP( $shortTitle ) ) {
- // An anonymous user
- $other->setNewtalk( true );
- } elseif ( $other->isLoggedIn() ) {
- $other->setNewtalk( true );
- } else {
- wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
- }
- }
- }
-
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- MessageCache::singleton()->replace( $shortTitle, $text );
+ $from = str_replace( '_', ' ', $fromP );
+ # User name given should match up with the top revision.
+ # If the user was deleted then $from should be empty.
+ if ( $from != $current->getUserText() ) {
+ $resultDetails = array( 'current' => $current );
+ return array( array( 'alreadyrolled',
+ htmlspecialchars( $this->mTitle->getPrefixedText() ),
+ htmlspecialchars( $fromP ),
+ htmlspecialchars( $current->getUserText() )
+ ) );
}
- if( $options['created'] ) {
- self::onArticleCreate( $this->mTitle );
- } else {
- self::onArticleEdit( $this->mTitle );
+ # Get the last edit not by this guy...
+ # Note: these may not be public values
+ $user = intval( $current->getRawUser() );
+ $user_text = $dbw->addQuotes( $current->getRawUserText() );
+ $s = $dbw->selectRow( 'revision',
+ array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
+ array( 'rev_page' => $current->getPage(),
+ "rev_user != {$user} OR rev_user_text != {$user_text}"
+ ), __METHOD__,
+ array( 'USE INDEX' => 'page_timestamp',
+ 'ORDER BY' => 'rev_timestamp DESC' )
+ );
+ if ( $s === false ) {
+ # No one else ever edited this page
+ return array( array( 'cantrollback' ) );
+ } elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
+ # Only admins can see this text
+ return array( array( 'notvisiblerev' ) );
}
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Perform article updates on a special page creation.
- *
- * @param $rev Revision object
- *
- * @todo This is a shitty interface function. Kill it and replace the
- * other shitty functions like doEditUpdates and such so it's not needed
- * anymore.
- * @deprecated since 1.18, use doEditUpdates()
- */
- public function createUpdates( $rev ) {
- wfDeprecated( __METHOD__, '1.18' );
- global $wgUser;
- $this->doEditUpdates( $rev, $wgUser, array( 'created' => true ) );
- }
-
- /**
- * This function is called right before saving the wikitext,
- * so we can do things like signatures and links-in-context.
- *
- * @deprecated in 1.19; use Parser::preSaveTransform() instead
- * @param $text String article contents
- * @param $user User object: user doing the edit
- * @param $popts ParserOptions object: parser options, default options for
- * the user loaded if null given
- * @return string article contents with altered wikitext markup (signatures
- * converted, {{subst:}}, templates, etc.)
- */
- public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
- global $wgParser, $wgUser;
+ $set = array();
+ if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
+ # Mark all reverted edits as bot
+ $set['rc_bot'] = 1;
+ }
- wfDeprecated( __METHOD__, '1.19' );
+ if ( $wgUseRCPatrol ) {
+ # Mark all reverted edits as patrolled
+ $set['rc_patrolled'] = 1;
+ }
- $user = is_null( $user ) ? $wgUser : $user;
+ if ( count( $set ) ) {
+ $dbw->update( 'recentchanges', $set,
+ array( /* WHERE */
+ 'rc_cur_id' => $current->getPage(),
+ 'rc_user_text' => $current->getUserText(),
+ "rc_timestamp > '{$s->rev_timestamp}'",
+ ), __METHOD__
+ );
+ }
- if ( $popts === null ) {
- $popts = ParserOptions::newFromUser( $user );
+ # Generate the edit summary if necessary
+ $target = Revision::newFromId( $s->rev_id );
+ if ( empty( $summary ) ) {
+ if ( $from == '' ) { // no public user name
+ $summary = wfMsgForContent( 'revertpage-nouser' );
+ } else {
+ $summary = wfMsgForContent( 'revertpage' );
+ }
}
- return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
- }
+ # Allow the custom summary to use the same args as the default message
+ $args = array(
+ $target->getUserText(), $from, $s->rev_id,
+ $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
+ $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
+ );
+ $summary = wfMsgReplaceArgs( $summary, $args );
- /**
- * Loads page_touched and returns a value indicating if it should be used
- * @return boolean true if not a redirect
- */
- public function checkTouched() {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
- }
- return !$this->mIsRedirect;
- }
+ # Save
+ $flags = EDIT_UPDATE;
- /**
- * Get the page_touched field
- * @return string containing GMT timestamp
- */
- public function getTouched() {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
+ if ( $guser->isAllowed( 'minoredit' ) ) {
+ $flags |= EDIT_MINOR;
}
- return $this->mTouched;
- }
- /**
- * Get the page_latest field
- * @return integer rev_id of current revision
- */
- public function getLatest() {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
+ if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
+ $flags |= EDIT_FORCE_BOT;
}
- return (int)$this->mLatest;
- }
- /**
- * Edit an article without doing all that other stuff
- * The article must already exist; link tables etc
- * are not updated, caches are not flushed.
- *
- * @param $text String: text submitted
- * @param $user User The relevant user
- * @param $comment String: comment submitted
- * @param $minor Boolean: whereas it's a minor modification
- */
- public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
- wfProfileIn( __METHOD__ );
+ # Actually store the edit
+ $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
+ if ( !empty( $status->value['revision'] ) ) {
+ $revId = $status->value['revision']->getId();
+ } else {
+ $revId = false;
+ }
- $dbw = wfGetDB( DB_MASTER );
- $revision = new Revision( array(
- 'page' => $this->getId(),
- 'text' => $text,
- 'comment' => $comment,
- 'minor_edit' => $minor ? 1 : 0,
- ) );
- $revision->insertOn( $dbw );
- $this->updateRevisionOn( $dbw, $revision );
+ wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+ $resultDetails = array(
+ 'summary' => $summary,
+ 'current' => $current,
+ 'target' => $target,
+ 'newid' => $revId
+ );
- wfProfileOut( __METHOD__ );
+ return array();
}
/**
return $reason;
}
- /**
- * Get parser options suitable for rendering the primary article wikitext
- * @param User|string $user User object or 'canonical'
- * @return ParserOptions
- */
- public function makeParserOptions( $user ) {
- global $wgContLang;
- if ( $user instanceof User ) { // settings per user (even anons)
- $options = ParserOptions::newFromUser( $user );
- } else { // canonical settings
- $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- }
- $options->enableLimitReport(); // show inclusion/loop reports
- $options->setTidy( true ); // fix bad HTML
- return $options;
- }
-
/**
* Update all the appropriate counts in the category table, given that
* we've added the categories $added and deleted the categories $deleted.
}
}
+ /**
+ * Perform article updates on a special page creation.
+ *
+ * @param $rev Revision object
+ *
+ * @todo This is a shitty interface function. Kill it and replace the
+ * other shitty functions like doEditUpdates and such so it's not needed
+ * anymore.
+ * @deprecated since 1.18, use doEditUpdates()
+ */
+ public function createUpdates( $rev ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ global $wgUser;
+ $this->doEditUpdates( $rev, $wgUser, array( 'created' => true ) );
+ }
+
+ /**
+ * This function is called right before saving the wikitext,
+ * so we can do things like signatures and links-in-context.
+ *
+ * @deprecated in 1.19; use Parser::preSaveTransform() instead
+ * @param $text String article contents
+ * @param $user User object: user doing the edit
+ * @param $popts ParserOptions object: parser options, default options for
+ * the user loaded if null given
+ * @return string article contents with altered wikitext markup (signatures
+ * converted, {{subst:}}, templates, etc.)
+ */
+ public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
+ global $wgParser, $wgUser;
+
+ wfDeprecated( __METHOD__, '1.19' );
+
+ $user = is_null( $user ) ? $wgUser : $user;
+
+ if ( $popts === null ) {
+ $popts = ParserOptions::newFromUser( $user );
+ }
+
+ return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+ }
+
+ /**
+ * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
+ *
+ * @deprecated in 1.19; use Title::isBigDeletion() instead.
+ * @return bool
+ */
+ public function isBigDeletion() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->mTitle->isBigDeletion();
+ }
+
+ /**
+ * Get the approximate revision count of this page.
+ *
+ * @deprecated in 1.19; use Title::estimateRevisionCount() instead.
+ * @return int
+ */
+ public function estimateRevisionCount() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->mTitle->estimateRevisionCount();
+ }
+
/**
* Update the article's restriction field, and leave a log entry.
*