From 7654ddf79816335d38ef84b6c2d41ad452fe6379 Mon Sep 17 00:00:00 2001 From: Alexandre Emsenhuber Date: Thu, 29 Dec 2011 14:21:15 +0000 Subject: [PATCH] * Group related functions * Put deprecated functions at the bottom --- includes/WikiPage.php | 1096 ++++++++++++++++++++--------------------- 1 file changed, 548 insertions(+), 548 deletions(-) diff --git a/includes/WikiPage.php b/includes/WikiPage.php index a99734d662..ee6713e2cb 100644 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@ -486,6 +486,39 @@ class WikiPage extends Page { } } + /** + * 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. @@ -638,6 +671,29 @@ class WikiPage extends Page { } } + /** + * 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 @@ -689,6 +745,58 @@ class WikiPage extends Page { 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? * @@ -745,6 +853,26 @@ class WikiPage extends Page { 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 */ @@ -876,29 +1004,6 @@ class WikiPage extends Page { 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. * @@ -1328,65 +1433,267 @@ class WikiPage extends Page { } /** - * 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; } } @@ -1560,109 +1867,35 @@ class WikiPage extends Page { } /** - * 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; @@ -1875,386 +2108,106 @@ class WikiPage extends Page { 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(); } /** @@ -2560,23 +2513,6 @@ class WikiPage extends Page { 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. @@ -2691,6 +2627,70 @@ class WikiPage extends Page { } } + /** + * 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. * -- 2.20.1