From 63601400e476c6cf43d985f3e7b9864681695ed4 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 18 Jan 2013 16:46:04 +0100 Subject: Update to MediaWiki 1.20.2 this update includes: * adjusted Arch Linux skin * updated FluxBBAuthPlugin * patch for https://bugzilla.wikimedia.org/show_bug.cgi?id=44024 --- includes/WikiPage.php | 629 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 413 insertions(+), 216 deletions(-) (limited to 'includes/WikiPage.php') diff --git a/includes/WikiPage.php b/includes/WikiPage.php index acc9831a..bc8766de 100644 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@ -1,4 +1,25 @@ selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ ); + if ( !$row ) { + return null; + } + return self::newFromRow( $row, $from ); + } + + /** + * Constructor from a database row + * + * @since 1.20 + * @param $row object: database row containing at least fields returned + * by selectFields(). + * @param $from string|int: source of $data: + * - "fromdb" or WikiPage::READ_NORMAL: from a slave DB + * - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB + * - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE * @return WikiPage */ - public static function newFromID( $id ) { - $t = Title::newFromID( $id ); - if ( $t ) { - return self::factory( $t ); + public static function newFromRow( $row, $from = 'fromdb' ) { + $page = self::factory( Title::newFromRow( $row ) ); + $page->loadFromRow( $row, $from ); + return $page; + } + + /** + * Convert 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants. + * + * @param $type object|string|int + * @return mixed + */ + private static function convertSelectType( $type ) { + switch ( $type ) { + case 'fromdb': + return self::READ_NORMAL; + case 'fromdbmaster': + return self::READ_LATEST; + case 'forupdate': + return self::READ_LOCKING; + default: + // It may already be an integer or whatever else + return $type; } - return null; } /** @@ -152,10 +200,20 @@ class WikiPage extends Page { /** * Clear the object + * @return void */ public function clear() { $this->mDataLoaded = false; + $this->mDataLoadedFrom = self::READ_NONE; + + $this->clearCacheFields(); + } + /** + * Clear the object cache fields + * @return void + */ + protected function clearCacheFields() { $this->mCounter = null; $this->mRedirectTarget = null; # Title object if set $this->mLastRevision = null; # Latest revision @@ -192,14 +250,15 @@ class WikiPage extends Page { * Fetch a page record with the given conditions * @param $dbr DatabaseBase object * @param $conditions Array + * @param $options Array * @return mixed Database result resource, or false on failure */ - protected function pageData( $dbr, $conditions ) { + protected function pageData( $dbr, $conditions, $options = array() ) { $fields = self::selectFields(); wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) ); - $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__ ); + $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options ); wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) ); @@ -212,12 +271,13 @@ class WikiPage extends Page { * * @param $dbr DatabaseBase object * @param $title Title object + * @param $options Array * @return mixed Database result resource, or false on failure */ - public function pageDataFromTitle( $dbr, $title ) { + public function pageDataFromTitle( $dbr, $title, $options = array() ) { return $this->pageData( $dbr, array( 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDBkey() ) ); + 'page_title' => $title->getDBkey() ), $options ); } /** @@ -225,37 +285,69 @@ class WikiPage extends Page { * * @param $dbr DatabaseBase * @param $id Integer + * @param $options Array * @return mixed Database result resource, or false on failure */ - public function pageDataFromId( $dbr, $id ) { - return $this->pageData( $dbr, array( 'page_id' => $id ) ); + public function pageDataFromId( $dbr, $id, $options = array() ) { + return $this->pageData( $dbr, array( 'page_id' => $id ), $options ); } /** * Set the general counter, title etc data loaded from * some source. * - * @param $data Object|String One of the following: - * A DB query result object or... - * "fromdb" to get from a slave DB or... - * "fromdbmaster" to get from the master DB + * @param $from object|string|int One of the following: + * - A DB query result object + * - "fromdb" or WikiPage::READ_NORMAL to get from a slave DB + * - "fromdbmaster" or WikiPage::READ_LATEST to get from the master DB + * - "forupdate" or WikiPage::READ_LOCKING to get from the master DB using SELECT FOR UPDATE + * * @return void */ - public function loadPageData( $data = 'fromdb' ) { - if ( $data === 'fromdbmaster' ) { + public function loadPageData( $from = 'fromdb' ) { + $from = self::convertSelectType( $from ); + if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) { + // We already have the data from the correct location, no need to load it twice. + return; + } + + if ( $from === self::READ_LOCKING ) { + $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle, array( 'FOR UPDATE' ) ); + } elseif ( $from === self::READ_LATEST ) { $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle ); - } elseif ( $data === 'fromdb' ) { // slave + } elseif ( $from === self::READ_NORMAL ) { $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle ); # Use a "last rev inserted" timestamp key to dimish the issue of slave lag. # Note that DB also stores the master position in the session and checks it. $touched = $this->getCachedLastEditTime(); if ( $touched ) { // key set if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) { + $from = self::READ_LATEST; $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle ); } } + } else { + // No idea from where the caller got this data, assume slave database. + $data = $from; + $from = self::READ_NORMAL; } + $this->loadFromRow( $data, $from ); + } + + /** + * Load the object from a database row + * + * @since 1.20 + * @param $data object: database row containing at least fields returned + * by selectFields() + * @param $from string|int One of the following: + * - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB + * - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB + * - "forupdate" or WikiPage::READ_LOCKING if the data comes from from + * the master DB using SELECT FOR UPDATE + */ + public function loadFromRow( $data, $from ) { $lc = LinkCache::singleton(); if ( $data ) { @@ -270,13 +362,22 @@ class WikiPage extends Page { $this->mTouched = wfTimestamp( TS_MW, $data->page_touched ); $this->mIsRedirect = intval( $data->page_is_redirect ); $this->mLatest = intval( $data->page_latest ); + // Bug 37225: $latest may no longer match the cached latest Revision object. + // Double-check the ID of any cached latest Revision object for consistency. + if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) { + $this->mLastRevision = null; + $this->mTimestamp = ''; + } } else { $lc->addBadLinkObj( $this->mTitle ); $this->mTitle->loadFromRow( false ); + + $this->clearCacheFields(); } $this->mDataLoaded = true; + $this->mDataLoadedFrom = self::convertSelectType( $from ); } /** @@ -367,6 +468,45 @@ class WikiPage extends Page { return (int)$this->mLatest; } + /** + * Get the Revision object of the oldest revision + * @return Revision|null + */ + public function getOldestRevision() { + wfProfileIn( __METHOD__ ); + + // Try using the slave database first, then try the master + $continue = 2; + $db = wfGetDB( DB_SLAVE ); + $revSelectFields = Revision::selectFields(); + + while ( $continue ) { + $row = $db->selectRow( + array( 'page', 'revision' ), + $revSelectFields, + array( + 'page_namespace' => $this->mTitle->getNamespace(), + 'page_title' => $this->mTitle->getDBkey(), + 'rev_page = page_id' + ), + __METHOD__, + array( + 'ORDER BY' => 'rev_timestamp ASC' + ) + ); + + if ( $row ) { + $continue = 0; + } else { + $db = wfGetDB( DB_MASTER ); + $continue--; + } + } + + wfProfileOut( __METHOD__ ); + return $row ? Revision::newFromRow( $row ) : null; + } + /** * Loads everything except the text * This isn't necessary for all uses, so it's only done if needed. @@ -381,7 +521,14 @@ class WikiPage extends Page { return; // page doesn't exist or is missing page_latest info } - $revision = Revision::newFromPageId( $this->getId(), $latest ); + // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the + // latest changes committed. This is true even within REPEATABLE-READ transactions, where + // S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to + // also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row + // UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT. + // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read. + $flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0; + $revision = Revision::newFromPageId( $this->getId(), $latest, $flags ); if ( $revision ) { // sanity $this->setLastEdit( $revision ); } @@ -414,7 +561,7 @@ class WikiPage extends Page { * Revision::FOR_PUBLIC to be displayed to all users * Revision::FOR_THIS_USER to be displayed to $wgUser * Revision::RAW get the text regardless of permissions - * @return String|false The text of the current revision + * @return String|bool The text of the current revision. False on failure */ public function getText( $audience = Revision::FOR_PUBLIC ) { $this->loadLastEdit(); @@ -427,7 +574,7 @@ class WikiPage extends Page { /** * Get the text of the current revision. No side-effects... * - * @return String|false The text of the current revision + * @return String|bool The text of the current revision. False on failure */ public function getRawText() { $this->loadLastEdit(); @@ -445,6 +592,7 @@ class WikiPage extends Page { if ( !$this->mTimestamp ) { $this->loadLastEdit(); } + return wfTimestamp( TS_MW, $this->mTimestamp ); } @@ -473,6 +621,24 @@ class WikiPage extends Page { } } + /** + * Get the User object of the user who created the page + * @param $audience Integer: one of: + * Revision::FOR_PUBLIC to be displayed to all users + * Revision::FOR_THIS_USER to be displayed to $wgUser + * Revision::RAW get the text regardless of permissions + * @return User|null + */ + public function getCreator( $audience = Revision::FOR_PUBLIC ) { + $revision = $this->getOldestRevision(); + if ( $revision ) { + $userName = $revision->getUserText( $audience ); + return User::newFromName( $userName, false ); + } else { + return null; + } + } + /** * @param $audience Integer: one of: * Revision::FOR_PUBLIC to be displayed to all users @@ -546,7 +712,7 @@ class WikiPage extends Page { * Determine whether a page would be suitable for being counted as an * article in the site_stats table based on the title & its content * - * @param $editInfo Object or false: object returned by prepareTextForEdit(), + * @param $editInfo Object|bool (false): object returned by prepareTextForEdit(), * if false, the current database state will be used * @return Boolean */ @@ -726,10 +892,10 @@ class WikiPage extends Page { $tables = array( 'revision', 'user' ); $fields = array( - 'rev_user as user_id', - 'rev_user_text AS user_name', + 'user_id' => 'rev_user', + 'user_name' => 'rev_user_text', $realNameField, - 'MAX(rev_timestamp) AS timestamp', + 'timestamp' => 'MAX(rev_timestamp)', ); $conds = array( 'rev_page' => $this->getId() ); @@ -888,6 +1054,7 @@ class WikiPage extends Page { /** * Perform the actions of a page purging + * @return bool */ public function doPurge() { global $wgUseSquid; @@ -903,7 +1070,7 @@ class WikiPage extends Page { if ( $wgUseSquid ) { // Commit the transaction before the purge is sent $dbw = wfGetDB( DB_MASTER ); - $dbw->commit(); + $dbw->commit( __METHOD__ ); // Send purge $update = SquidUpdate::newSimplePurge( $this->mTitle ); @@ -1002,7 +1169,7 @@ class WikiPage extends Page { $conditions, __METHOD__ ); - $result = $dbw->affectedRows() != 0; + $result = $dbw->affectedRows() > 0; if ( $result ) { $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect ); $this->setLastEdit( $revision ); @@ -1023,7 +1190,7 @@ class WikiPage extends Page { * @param $dbw DatabaseBase * @param $redirectTitle Title object pointing to the redirect target, * or NULL if this is not a redirect - * @param $lastRevIsRedirect If given, will optimize adding and + * @param $lastRevIsRedirect null|bool If given, will optimize adding and * removing rows in redirect table. * @return bool true on success, false on failure * @private @@ -1059,7 +1226,7 @@ class WikiPage extends Page { * If the given revision is newer than the currently set page_latest, * update the page record. Otherwise, do nothing. * - * @param $dbw Database object + * @param $dbw DatabaseBase object * @param $revision Revision object * @return mixed */ @@ -1124,7 +1291,7 @@ class WikiPage extends Page { } /** - * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...) + * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...) * @param $text String: new text of the section * @param $sectionTitle String: new section's subject, only if $section is 'new' * @param $edittime String: revision timestamp or null to use the current revision @@ -1160,7 +1327,8 @@ class WikiPage extends Page { if ( $section == 'new' ) { # Inserting a new section - $subject = $sectionTitle ? wfMsgForContent( 'newsectionheaderdefaultlevel', $sectionTitle ) . "\n\n" : ''; + $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' ) + ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : ''; if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) { $text = strlen( trim( $oldtext ) ) > 0 ? "{$oldtext}\n\n{$subject}{$text}" @@ -1223,9 +1391,10 @@ class WikiPage extends Page { * edit-already-exists error will be returned. 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 + * @param bool|int $baseRevId int the revision ID this edit was based off, if any * @param $user User the user doing the edit * + * @throws MWException * @return Status object. Possible errors: * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status * edit-gone-missing: In update mode, but the article didn't exist @@ -1242,7 +1411,7 @@ class WikiPage extends Page { * Compatibility note: this function previously returned a boolean value indicating success/failure */ public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) { - global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries; + global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol; # Low-level sanity check if ( $this->mTitle->getText() === '' ) { @@ -1254,7 +1423,9 @@ class WikiPage extends Page { $user = is_null( $user ) ? $wgUser : $user; $status = Status::newGood( array() ); - # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already + // Load the data from the master database if needed. + // The caller may already loaded it from the master or even loaded it using + // SELECT FOR UPDATE, so do not override that using clear(). $this->loadPageData( 'fromdbmaster' ); $flags = $this->checkFlags( $flags ); @@ -1306,11 +1477,10 @@ class WikiPage extends Page { wfProfileOut( __METHOD__ ); return $status; - } - - # Make sure the revision is either completely inserted or not inserted at all - if ( !$wgDBtransactions ) { - $userAbort = ignore_user_abort( true ); + } elseif ( $oldtext === false ) { + # Sanity check for bug 37225 + wfProfileOut( __METHOD__ ); + throw new MWException( "Could not find text for current revision {$oldid}." ); } $revision = new Revision( array( @@ -1323,11 +1493,14 @@ class WikiPage extends Page { 'user_text' => $user->getName(), 'timestamp' => $now ) ); + # Bug 37225: use accessor to get the text as Revision may trim it. + # After trimming, the text may be a duplicate of the current text. + $text = $revision->getText(); // sanity; EditPage should trim already $changed = ( strcmp( $text, $oldtext ) != 0 ); if ( $changed ) { - $dbw->begin(); + $dbw->begin( __METHOD__ ); $revisionId = $revision->insertOn( $dbw ); # Update page @@ -1340,54 +1513,40 @@ class WikiPage extends Page { $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect ); if ( !$ok ) { - /* Belated edit conflict! Run away!! */ + # Belated edit conflict! Run away!! $status->fatal( 'edit-conflict' ); - # Delete the invalid revision if the DB is not transactional - if ( !$wgDBtransactions ) { - $dbw->delete( 'revision', array( 'rev_id' => $revisionId ), __METHOD__ ); - } + $dbw->rollback( __METHOD__ ); - $revisionId = 0; - $dbw->rollback(); - } else { - global $wgUseRCPatrol; - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) ); - # Update recentchanges - if ( !( $flags & EDIT_SUPPRESS_RC ) ) { - # Mark as patrolled if the user can do so - $patrolled = $wgUseRCPatrol && !count( - $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); - # Add RC row to the DB - $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary, - $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize, - $revisionId, $patrolled - ); - - # Log auto-patrolled edits - if ( $patrolled ) { - PatrolLog::record( $rc, true ); - } + wfProfileOut( __METHOD__ ); + return $status; + } + + wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) ); + # Update recentchanges + if ( !( $flags & EDIT_SUPPRESS_RC ) ) { + # Mark as patrolled if the user can do so + $patrolled = $wgUseRCPatrol && !count( + $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); + # Add RC row to the DB + $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary, + $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize, + $revisionId, $patrolled + ); + + # Log auto-patrolled edits + if ( $patrolled ) { + PatrolLog::record( $rc, true, $user ); } - $user->incEditCount(); - $dbw->commit(); } + $user->incEditCount(); + $dbw->commit( __METHOD__ ); } else { // Bug 32948: revision ID must be set to page {{REVISIONID}} and // related variables correctly $revision->setId( $this->getLatest() ); } - if ( !$wgDBtransactions ) { - ignore_user_abort( $userAbort ); - } - - // Now that ignore_user_abort is restored, we can respond to fatal errors - if ( !$status->isOK() ) { - wfProfileOut( __METHOD__ ); - return $status; - } - # Update links tables, site stats, etc. $this->doEditUpdates( $revision, $user, array( 'changed' => $changed, 'oldcountable' => $oldcountable ) ); @@ -1403,14 +1562,14 @@ class WikiPage extends Page { # Create new article $status->value['new'] = true; - $dbw->begin(); + $dbw->begin( __METHOD__ ); # Add the page record; stake our claim on this title! # This will return false if the article already exists $newid = $this->insertOn( $dbw ); if ( $newid === false ) { - $dbw->rollback(); + $dbw->rollback( __METHOD__ ); $status->fatal( 'edit-already-exists' ); wfProfileOut( __METHOD__ ); @@ -1429,6 +1588,9 @@ class WikiPage extends Page { ) ); $revisionId = $revision->insertOn( $dbw ); + # Bug 37225: use accessor to get the text as Revision may trim it + $text = $revision->getText(); // sanity; EditPage should trim already + # Update the page record with revision data $this->updateRevisionOn( $dbw, $revision, 0 ); @@ -1436,8 +1598,6 @@ class WikiPage extends Page { # Update recentchanges if ( !( $flags & EDIT_SUPPRESS_RC ) ) { - global $wgUseRCPatrol, $wgUseNPPatrol; - # Mark as patrolled if the user can do so $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count( $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); @@ -1447,11 +1607,11 @@ class WikiPage extends Page { # Log auto-patrolled edits if ( $patrolled ) { - PatrolLog::record( $rc, true ); + PatrolLog::record( $rc, true, $user ); } } $user->incEditCount(); - $dbw->commit(); + $dbw->commit( __METHOD__ ); # Update links, etc. $this->doEditUpdates( $revision, $user, array( 'created' => true ) ); @@ -1480,24 +1640,41 @@ class WikiPage extends Page { /** * Get parser options suitable for rendering the primary article wikitext - * @param User|string $user User object or 'canonical' + * + * @param IContextSource|User|string $context One of the following: + * - IContextSource: Use the User and the Language of the provided + * context + * - User: Use the provided User object and $wgLang for the language, + * so use an IContextSource object if possible. + * - 'canonical': Canonical options (anonymous user with default + * preferences and content language). * @return ParserOptions */ - public function makeParserOptions( $user ) { + public function makeParserOptions( $context ) { global $wgContLang; - if ( $user instanceof User ) { // settings per user (even anons) - $options = ParserOptions::newFromUser( $user ); + + if ( $context instanceof IContextSource ) { + $options = ParserOptions::newFromContext( $context ); + } elseif ( $context instanceof User ) { // settings per user (even anons) + $options = ParserOptions::newFromUser( $context ); } else { // canonical settings $options = ParserOptions::newFromUserAndLang( new User, $wgContLang ); } + + if ( $this->getTitle()->isConversionTable() ) { + $options->disableContentConversion(); + } + $options->enableLimitReport(); // show inclusion/loop reports $options->setTidy( true ); // fix bad HTML + return $options; } /** * Prepare text which is about to be saved. * Returns a stdclass with source, pst and output members + * @return bool|object */ public function prepareTextForEdit( $text, $revid = null, User $user = null ) { global $wgParser, $wgContLang, $wgUser; @@ -1568,9 +1745,9 @@ class WikiPage extends Page { $parserCache->save( $editInfo->output, $this, $editInfo->popts ); } - # Update the links tables - $u = new LinksUpdate( $this->mTitle, $editInfo->output ); - $u->doUpdate(); + # Update the links tables and other secondary data + $updates = $editInfo->output->getSecondaryDataUpdates( $this->mTitle ); + DataUpdate::runUpdates( $updates ); wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) ); @@ -1630,9 +1807,9 @@ class WikiPage extends Page { wfDebug( __METHOD__ . ": invalid username\n" ); } elseif ( User::isIP( $shortTitle ) ) { // An anonymous user - $other->setNewtalk( true ); + $other->setNewtalk( true, $revision ); } elseif ( $other->isLoggedIn() ) { - $other->setNewtalk( true ); + $other->setNewtalk( true, $revision ); } else { wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" ); } @@ -1689,7 +1866,7 @@ class WikiPage extends Page { * @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 + * @return Status */ public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) { global $wgContLang; @@ -1772,12 +1949,15 @@ class WikiPage extends Page { if ( $restrictions != '' ) { $protectDescription .= $wgContLang->getDirMark() . "[$action=$restrictions] ("; if ( $encodedExpiry[$action] != 'infinity' ) { - $protectDescription .= wfMsgForContent( 'protect-expiring', + $protectDescription .= wfMessage( + 'protect-expiring', $wgContLang->timeanddate( $expiry[$action], false, false ) , $wgContLang->date( $expiry[$action], false, false ) , - $wgContLang->time( $expiry[$action], false, false ) ); + $wgContLang->time( $expiry[$action], false, false ) + )->inContentLanguage()->text(); } else { - $protectDescription .= wfMsgForContent( 'protect-expiry-indefinite' ); + $protectDescription .= wfMessage( 'protect-expiry-indefinite' ) + ->inContentLanguage()->text(); } $protectDescription .= ') '; @@ -1818,7 +1998,12 @@ class WikiPage extends Page { } # Prepare a null revision to be added to the history - $editComment = $wgContLang->ucfirst( wfMsgForContent( $revCommentMsg, $this->mTitle->getPrefixedText() ) ); + $editComment = $wgContLang->ucfirst( + wfMessage( + $revCommentMsg, + $this->mTitle->getPrefixedText() + )->inContentLanguage()->text() + ); if ( $reason ) { $editComment .= ": $reason"; } @@ -1826,7 +2011,9 @@ class WikiPage extends Page { $editComment .= " ($protectDescription)"; } if ( $cascade ) { - $editComment .= ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']'; + // FIXME: Should use 'brackets' message. + $editComment .= ' [' . wfMessage( 'protect-summary-cascade' ) + ->inContentLanguage()->text() . ']'; } # Insert a null revision @@ -1893,6 +2080,7 @@ class WikiPage extends Page { * Take an array of page restrictions and flatten it to a string * suitable for insertion into the page_restrictions field. * @param $limit Array + * @throws MWException * @return String */ protected static function flattenRestrictions( $limit ) { @@ -1913,15 +2101,15 @@ class WikiPage extends Page { } /** - * Same as doDeleteArticleReal(), but returns more detailed success/failure status + * Same as doDeleteArticleReal(), but returns a simple boolean. This is kept around for + * backwards compatibility, if you care about error reporting you should use + * doDeleteArticleReal() instead. + * * 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 $suppress boolean suppress all revisions and log the deletion in + * the suppression log instead of the deletion log * @param $id int article ID * @param $commit boolean defaults to true, triggers transaction end * @param &$error Array of errors to append to @@ -1931,43 +2119,56 @@ class WikiPage extends Page { public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null ) { - return $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user ) - == WikiPage::DELETE_SUCCESS; + $status = $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user ); + return $status->isGood(); } /** * Back-end article deletion * Deletes the article with database consistency, writes logs, purges caches * + * @since 1.19 + * * @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 $suppress boolean suppress all revisions and log the deletion in + * the suppression log instead of the deletion log * @param $commit boolean defaults to true, triggers transaction end * @param &$error Array of errors to append to * @param $user User The deleting user - * @return int: One of WikiPage::DELETE_* constants + * @return Status: Status object; if successful, $status->value is the log_id of the + * deletion log entry. If the page couldn't be deleted because it wasn't + * found, $status is a non-fatal 'cannotdelete' error */ public function doDeleteArticleReal( $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 WikiPage::DELETE_HOOK_ABORTED; + $status = Status::newGood(); + + if ( $this->mTitle->getDBkey() === '' ) { + $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) ); + return $status; } - $dbw = wfGetDB( DB_MASTER ); - $t = $this->mTitle->getDBkey(); - $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE ); - if ( $t === '' || $id == 0 ) { - return WikiPage::DELETE_NO_PAGE; + $user = is_null( $user ) ? $wgUser : $user; + if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) { + if ( $status->isOK() ) { + // Hook aborted but didn't set a fatal status + $status->fatal( 'delete-hook-aborted' ); + } + return $status; + } + + if ( $id == 0 ) { + $this->loadPageData( 'forupdate' ); + $id = $this->getID(); + if ( $id == 0 ) { + $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) ); + return $status; + } } // Bitfields to further suppress the content @@ -1982,7 +2183,8 @@ class WikiPage extends Page { $bitfield = 'rev_deleted'; } - $dbw->begin(); + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin( __METHOD__ ); // 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 @@ -2019,11 +2221,12 @@ class WikiPage extends Page { # 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 + $ok = ( $dbw->affectedRows() > 0 ); // getArticleID() uses slave, could be laggy if ( !$ok ) { - $dbw->rollback(); - return WikiPage::DELETE_NO_REVISIONS; + $dbw->rollback( __METHOD__ ); + $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) ); + return $status; } $this->doDeleteUpdates( $id ); @@ -2039,79 +2242,54 @@ class WikiPage extends Page { $logEntry->publish( $logid ); if ( $commit ) { - $dbw->commit(); + $dbw->commit( __METHOD__ ); } wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) ); - return WikiPage::DELETE_SUCCESS; + $status->value = $logid; + return $status; } /** * Do some database updates after deletion * - * @param $id Int: page_id value of the page being deleted + * @param $id Int: page_id value of the page being deleted (B/C, currently unused) */ public function doDeleteUpdates( $id ) { + # update site status DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) ); - $dbw = wfGetDB( DB_MASTER ); - - # 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 ); - - # If using cascading deletes, we can skip some explicit deletes - if ( !$dbw->cascadingDeletes() ) { - $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ ); - - # Delete outgoing links - $dbw->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ ); - $dbw->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ ); - $dbw->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ ); - $dbw->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ ); - $dbw->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ ); - $dbw->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ ); - $dbw->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ ); - $dbw->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ ); - $dbw->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ ); - } - - # If using cleanup triggers, we can skip some manual deletes - if ( !$dbw->cleanupTriggers() ) { - # Clean up recentchanges entries... - $dbw->delete( 'recentchanges', - array( 'rc_type != ' . RC_LOG, - 'rc_namespace' => $this->mTitle->getNamespace(), - 'rc_title' => $this->mTitle->getDBkey() ), - __METHOD__ ); - $dbw->delete( 'recentchanges', - array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ), - __METHOD__ ); - } + # remove secondary indexes, etc + $updates = $this->getDeletionUpdates( ); + DataUpdate::runUpdates( $updates ); # Clear caches - self::onArticleDelete( $this->mTitle ); + WikiPage::onArticleDelete( $this->mTitle ); + + # Reset this object + $this->clear(); # Clear the cached article id so the interface doesn't act like we exist $this->mTitle->resetArticleID( 0 ); } + public function getDeletionUpdates() { + $updates = array( + new LinksDeletionUpdate( $this ), + ); + + //@todo: make a hook to add update objects + //NOTE: deletion updates will be determined by the ContentHandler in the future + return $updates; + } + /** * Roll back the most recent consecutive set of edits to a page * from the same user; fails if there are no eligible edits to * roll back to, e.g. user is the sole contributor. This function * performs permissions checks on $user, then calls commitRollback() * to do the dirty work - * + * * @todo: seperate the business/permission stuff out from backend code * * @param $fromP String: Name of the user whose edits to rollback. @@ -2169,6 +2347,7 @@ class WikiPage extends Page { * * @param $resultDetails Array: contains result-specific array of additional values * @param $guser User The user performing the rollback + * @return array */ public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) { global $wgUseRCPatrol, $wgContLang; @@ -2234,7 +2413,7 @@ class WikiPage extends Page { array( /* WHERE */ 'rc_cur_id' => $current->getPage(), 'rc_user_text' => $current->getUserText(), - "rc_timestamp > '{$s->rev_timestamp}'", + 'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ), ), __METHOD__ ); } @@ -2243,9 +2422,9 @@ class WikiPage extends Page { $target = Revision::newFromId( $s->rev_id ); if ( empty( $summary ) ) { if ( $from == '' ) { // no public user name - $summary = wfMsgForContent( 'revertpage-nouser' ); + $summary = wfMessage( 'revertpage-nouser' ); } else { - $summary = wfMsgForContent( 'revertpage' ); + $summary = wfMessage( 'revertpage' ); } } @@ -2255,7 +2434,14 @@ class WikiPage extends Page { $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ), $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() ) ); - $summary = wfMsgReplaceArgs( $summary, $args ); + if( $summary instanceof Message ) { + $summary = $summary->params( $args )->inContentLanguage()->text(); + } else { + $summary = wfMsgReplaceArgs( $summary, $args ); + } + + # Truncate for whole multibyte characters. + $summary = $wgContLang->truncate( $summary, 255 ); # Save $flags = EDIT_UPDATE; @@ -2432,11 +2618,12 @@ class WikiPage extends Page { if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) { $truncatedtext = $wgContLang->truncate( str_replace( "\n", ' ', $newtext ), - max( 0, 250 - - strlen( wfMsgForContent( 'autoredircomment' ) ) + max( 0, 255 + - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() ) - strlen( $rt->getFullText() ) ) ); - return wfMsgForContent( 'autoredircomment', $rt->getFullText(), $truncatedtext ); + return wfMessage( 'autoredircomment', $rt->getFullText() ) + ->rawParams( $truncatedtext )->inContentLanguage()->text(); } # New page autosummaries @@ -2445,22 +2632,24 @@ class WikiPage extends Page { $truncatedtext = $wgContLang->truncate( str_replace( "\n", ' ', $newtext ), - max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) ); + max( 0, 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ) ); - return wfMsgForContent( 'autosumm-new', $truncatedtext ); + return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext ) + ->inContentLanguage()->text(); } # Blanking autosummaries if ( $oldtext != '' && $newtext == '' ) { - return wfMsgForContent( 'autosumm-blank' ); + return wfMessage( 'autosumm-blank' )->inContentLanguage()->text(); } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) { # Removing more than 90% of the article $truncatedtext = $wgContLang->truncate( $newtext, - max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) ); + max( 0, 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) ) ); - return wfMsgForContent( 'autosumm-replace', $truncatedtext ); + return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext ) + ->inContentLanguage()->text(); } # If we reach this point, there's no applicable autosummary for our case, so our @@ -2535,12 +2724,16 @@ class WikiPage extends Page { if ( $blank ) { // The current revision is blank and the one before is also // blank. It's just not our lucky day - $reason = wfMsgForContent( 'exbeforeblank', '$1' ); + $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text(); } else { if ( $onlyAuthor ) { - $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor ); + $reason = wfMessage( + 'excontentauthor', + '$1', + $onlyAuthor + )->inContentLanguage()->text(); } else { - $reason = wfMsgForContent( 'excontent', '$1' ); + $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text(); } } @@ -2672,6 +2865,7 @@ class WikiPage extends Page { if ( count( $templates_diff ) > 0 ) { # Whee, link updates time. + # Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output. $u = new LinksUpdate( $this->mTitle, $parserOutput, false ); $u->doUpdate(); } @@ -2779,7 +2973,7 @@ class WikiPage extends Page { public function quickEdit( $text, $comment = '', $minor = 0 ) { wfDeprecated( __METHOD__, '1.18' ); global $wgUser; - return $this->doQuickEdit( $text, $wgUser, $comment, $minor ); + $this->doQuickEdit( $text, $wgUser, $comment, $minor ); } /** @@ -2793,6 +2987,8 @@ class WikiPage extends Page { /** * @deprecated since 1.18 + * @param $oldid int + * @return bool */ public function useParserCache( $oldid ) { wfDeprecated( __METHOD__, '1.18' ); @@ -2829,7 +3025,7 @@ class PoolWorkArticleView extends PoolCounterWork { private $text; /** - * @var ParserOutput|false + * @var ParserOutput|bool */ private $parserOutput = false; @@ -2839,7 +3035,7 @@ class PoolWorkArticleView extends PoolCounterWork { private $isDirty = false; /** - * @var Status|false + * @var Status|bool */ private $error = false; @@ -2883,7 +3079,7 @@ class PoolWorkArticleView extends PoolCounterWork { /** * Get a Status object in case of error or false otherwise * - * @return Status|false + * @return Status|bool */ public function getError() { return $this->error; @@ -2973,6 +3169,7 @@ class PoolWorkArticleView extends PoolCounterWork { /** * @param $status Status + * @return bool */ function error( $status ) { $this->error = $status; -- cgit v1.2.3-54-g00ecf