r41244 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r41243‎ | r41244 | r41245 >
Date:10:15, 25 September 2008
Author:tstarling
Status:old
Tags:
Comment:
Re r41198: if you're going to break the interface, you may as well do it properly. Rather than Article::doEdit() returning some random fragment of data that your current application requires, how about we have it return a general, extensible object?

Also:
* Fixed EDIT_NEW on existing article to return an error status instead of throw a DB exception
* Fixed a bug dating from MW 1.5 whereby there's a short interval where an edit conflict can be missed, and an edit overwritten. See comment before updateRevisionOn() call.
* Reduced some indenting levels using early returns
Modified paths:
  • /trunk/phase3/includes/Article.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /trunk/phase3/maintenance/edit.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/edit.php
@@ -58,15 +58,20 @@
5959
6060 # Do the edit
6161 print "Saving... ";
62 -$success = $wgArticle->doEdit( $text, $summary,
 62+$status = $wgArticle->doEdit( $text, $summary,
6363 ( $minor ? EDIT_MINOR : 0 ) |
6464 ( $bot ? EDIT_FORCE_BOT : 0 ) |
6565 ( $autoSummary ? EDIT_AUTOSUMMARY : 0 ) |
6666 ( $noRC ? EDIT_SUPPRESS_RC : 0 ) );
67 -if ( $success ) {
 67+if ( $status->isOK() ) {
6868 print "done\n";
 69+ $exit = 0;
6970 } else {
7071 print "failed\n";
71 - exit( 1 );
 72+ $exit = 1;
7273 }
 74+if ( !$status->isGood() ) {
 75+ print $status->getWikiText() . "\n";
 76+}
 77+exit( $exit );
7378
Index: trunk/phase3/includes/Article.php
@@ -1125,7 +1125,7 @@
11261126 * Best if all done inside a transaction.
11271127 *
11281128 * @param Database $dbw
1129 - * @return int The newly created page_id key
 1129+ * @return int The newly created page_id key, or false if the title already existed
11301130 * @private
11311131 */
11321132 function insertOn( $dbw ) {
@@ -1144,13 +1144,15 @@
11451145 'page_touched' => $dbw->timestamp(),
11461146 'page_latest' => 0, # Fill this in shortly...
11471147 'page_len' => 0, # Fill this in shortly...
1148 - ), __METHOD__ );
1149 - $newid = $dbw->insertId();
 1148+ ), __METHOD__, 'IGNORE' );
11501149
1151 - $this->mTitle->resetArticleId( $newid );
1152 -
 1150+ $affected = $dbw->affectedRows();
 1151+ if ( $affected ) {
 1152+ $newid = $dbw->insertId();
 1153+ $this->mTitle->resetArticleId( $newid );
 1154+ }
11531155 wfProfileOut( __METHOD__ );
1154 - return $newid;
 1156+ return $affected ? $newid : false;
11551157 }
11561158
11571159 /**
@@ -1363,29 +1365,31 @@
13641366 ( $minor ? EDIT_MINOR : 0 ) |
13651367 ( $forceBot ? EDIT_FORCE_BOT : 0 );
13661368
1367 - $good = $this->doEdit( $text, $summary, $flags );
1368 - if ( $good ) {
1369 - $dbw = wfGetDB( DB_MASTER );
1370 - if ($watchthis) {
1371 - if (!$this->mTitle->userIsWatching()) {
1372 - $dbw->begin();
1373 - $this->doWatch();
1374 - $dbw->commit();
1375 - }
1376 - } else {
1377 - if ( $this->mTitle->userIsWatching() ) {
1378 - $dbw->begin();
1379 - $this->doUnwatch();
1380 - $dbw->commit();
1381 - }
 1369+ $status = $this->doEdit( $text, $summary, $flags );
 1370+ if ( !$status->isOK() ) {
 1371+ return false;
 1372+ }
 1373+
 1374+ $dbw = wfGetDB( DB_MASTER );
 1375+ if ($watchthis) {
 1376+ if (!$this->mTitle->userIsWatching()) {
 1377+ $dbw->begin();
 1378+ $this->doWatch();
 1379+ $dbw->commit();
13821380 }
 1381+ } else {
 1382+ if ( $this->mTitle->userIsWatching() ) {
 1383+ $dbw->begin();
 1384+ $this->doUnwatch();
 1385+ $dbw->commit();
 1386+ }
 1387+ }
13831388
1384 - $extraQuery = ''; // Give extensions a chance to modify URL query on update
1385 - wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraQuery ) );
 1389+ $extraQuery = ''; // Give extensions a chance to modify URL query on update
 1390+ wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraQuery ) );
13861391
1387 - $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraQuery );
1388 - }
1389 - return $good;
 1392+ $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraQuery );
 1393+ return true;
13901394 }
13911395
13921396 /**
@@ -1415,13 +1419,27 @@
14161420 * Fill in blank summaries with generated text where possible
14171421 *
14181422 * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
1419 - * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If
1420 - * EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception
1421 - * to be thrown from the Database. These two conditions are also possible with auto-detection due
1422 - * to MediaWiki's performance-optimised locking strategy.
 1423+ * If EDIT_UPDATE is specified and the article doesn't exist, the function will an
 1424+ * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
 1425+ * edit-already-exists error will be returned. These two conditions are also possible with
 1426+ * auto-detection due to MediaWiki's performance-optimised locking strategy.
 1427+ *
14231428 * @param $baseRevId, the revision ID this edit was based off, if any
14241429 *
1425 - * @return int Current revision ID after this edit
 1430+ * @return Status object. Possible errors:
 1431+ * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
 1432+ * edit-gone-missing: In update mode, but the article didn't exist
 1433+ * edit-conflict: In update mode, the article changed unexpectedly
 1434+ * edit-no-change: Warning that the text was the same as before
 1435+ * edit-already-exists: In creation mode, but the article already exists
 1436+ *
 1437+ * Extensions may define additional errors.
 1438+ *
 1439+ * $return->value will contain an associative array with members as follows:
 1440+ * new: Boolean indicating if the function attempted to create a new article
 1441+ * revision: The revision object for the inserted revision, or null
 1442+ *
 1443+ * Compatibility note: this function previously returned a boolean value indicating success/failure
14261444 */
14271445 function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
14281446 global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
@@ -1436,10 +1454,13 @@
14371455 if ($user == null) {
14381456 $user = $wgUser;
14391457 }
1440 - $good = true;
 1458+ $status = Status::newGood( array() );
14411459
 1460+ # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
 1461+ $this->loadPageData();
 1462+
14421463 if ( !($flags & EDIT_NEW) && !($flags & EDIT_UPDATE) ) {
1443 - $aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE );
 1464+ $aid = $this->mTitle->getArticleID();
14441465 if ( $aid ) {
14451466 $flags |= EDIT_UPDATE;
14461467 } else {
@@ -1449,11 +1470,14 @@
14501471
14511472 if( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text,
14521473 &$summary, $flags & EDIT_MINOR,
1453 - null, null, &$flags ) ) )
 1474+ null, null, &$flags, &$status ) ) )
14541475 {
14551476 wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
14561477 wfProfileOut( __METHOD__ );
1457 - return false;
 1478+ if ( $status->isOK() ) {
 1479+ $status->fatal( 'edit-hook-aborted');
 1480+ }
 1481+ return $status;
14581482 }
14591483
14601484 # Silently ignore EDIT_MINOR if not allowed
@@ -1477,13 +1501,13 @@
14781502
14791503 if ( $flags & EDIT_UPDATE ) {
14801504 # Update article, but only if changed.
 1505+ $status->value['new'] = false;
14811506
14821507 # Make sure the revision is either completely inserted or not inserted at all
14831508 if( !$wgDBtransactions ) {
14841509 $userAbort = ignore_user_abort( true );
14851510 }
14861511
1487 - $lastRevision = 0;
14881512 $revisionId = 0;
14891513
14901514 $changed = ( strcmp( $text, $oldtext ) != 0 );
@@ -1493,14 +1517,12 @@
14941518 - (int)$this->isCountable( $oldtext );
14951519 $this->mTotalAdjustment = 0;
14961520
1497 - $lastRevision = $dbw->selectField(
1498 - 'page', 'page_latest', array( 'page_id' => $this->getId() ) );
1499 -
1500 - if ( !$lastRevision ) {
 1521+ if ( !$this->mLatest ) {
15011522 # Article gone missing
15021523 wfDebug( __METHOD__.": EDIT_UPDATE specified but article doesn't exist\n" );
 1524+ $status->fatal( 'edit-gone-missing' );
15031525 wfProfileOut( __METHOD__ );
1504 - return false;
 1526+ return $status;
15051527 }
15061528
15071529 $revision = new Revision( array(
@@ -1508,7 +1530,7 @@
15091531 'comment' => $summary,
15101532 'minor_edit' => $isminor,
15111533 'text' => $text,
1512 - 'parent_id' => $lastRevision,
 1534+ 'parent_id' => $this->mLatest,
15131535 'user' => $user->getId(),
15141536 'user_text' => $user->getName(),
15151537 ) );
@@ -1517,11 +1539,21 @@
15181540 $revisionId = $revision->insertOn( $dbw );
15191541
15201542 # Update page
1521 - $ok = $this->updateRevisionOn( $dbw, $revision, $lastRevision );
 1543+ #
 1544+ # Note that we use $this->mLatest instead of fetching a value from the master DB
 1545+ # during the course of this function. This makes sure that EditPage can detect
 1546+ # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
 1547+ # before this function is called. A previous function used a separate query, this
 1548+ # creates a window where concurrent edits can cause an ignored edit conflict.
 1549+ $ok = $this->updateRevisionOn( $dbw, $revision, $this->mLatest );
15221550
15231551 if( !$ok ) {
15241552 /* Belated edit conflict! Run away!! */
1525 - $good = false;
 1553+ $status->fatal( 'edit-conflict' );
 1554+ # Delete the invalid revision if the DB is not transactional
 1555+ if ( !$wgDBtransactions ) {
 1556+ $dbw->delete( 'revision', array( 'rev_id' => $revisionId ), __METHOD__ );
 1557+ }
15261558 $revisionId = 0;
15271559 $dbw->rollback();
15281560 } else {
@@ -1530,7 +1562,7 @@
15311563 # Update recentchanges
15321564 if( !( $flags & EDIT_SUPPRESS_RC ) ) {
15331565 $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
1534 - $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
 1566+ $this->mLatest, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
15351567 $revisionId );
15361568
15371569 # Mark as patrolled if the user can do so
@@ -1542,6 +1574,7 @@
15431575 $dbw->commit();
15441576 }
15451577 } else {
 1578+ $status->warning( 'edit-no-change' );
15461579 $revision = null;
15471580 // Keep the same revision ID, but do some updates on it
15481581 $revisionId = $this->getRevIdFetched();
@@ -1553,16 +1586,20 @@
15541587 if( !$wgDBtransactions ) {
15551588 ignore_user_abort( $userAbort );
15561589 }
 1590+ // Now that ignore_user_abort is restored, we can respond to fatal errors
 1591+ if ( !$status->isOK() ) {
 1592+ wfProfileOut( __METHOD__ );
 1593+ return $status;
 1594+ }
15571595
1558 - if ( $good ) {
1559 - # Invalidate cache of this article and all pages using this article
1560 - # as a template. Partly deferred.
1561 - Article::onArticleEdit( $this->mTitle, false ); // leave templatelinks for editUpdates()
1562 - # Update links tables, site stats, etc.
1563 - $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
1564 - }
 1596+ # Invalidate cache of this article and all pages using this article
 1597+ # as a template. Partly deferred.
 1598+ Article::onArticleEdit( $this->mTitle, false ); // leave templatelinks for editUpdates()
 1599+ # Update links tables, site stats, etc.
 1600+ $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
15651601 } else {
15661602 # Create new article
 1603+ $status->value['new'] = true;
15671604
15681605 # Set statistics members
15691606 # We work out if it's countable after PST to avoid counter drift
@@ -1571,10 +1608,18 @@
15721609 $this->mTotalAdjustment = 1;
15731610
15741611 $dbw->begin();
 1612+
15751613 # Add the page record; stake our claim on this title!
1576 - # This will fail with a database query exception if the article already exists
 1614+ # This will return false if the article already exists
15771615 $newid = $this->insertOn( $dbw );
15781616
 1617+ if ( $newid === false ) {
 1618+ $dbw->rollback();
 1619+ $status->fatal( 'edit-already-exists' );
 1620+ wfProfileOut( __METHOD__ );
 1621+ return $status;
 1622+ }
 1623+
15791624 # Save the revision text...
15801625 $revision = new Revision( array(
15811626 'page' => $newid,
@@ -1614,17 +1659,19 @@
16151660 $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
16161661 }
16171662
1618 - if ( $good && !( $flags & EDIT_DEFER_UPDATES ) ) {
 1663+ # Do updates right now unless deferral was requested
 1664+ if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
16191665 wfDoUpdates();
16201666 }
16211667
1622 - if ( $good ) {
1623 - wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
1624 - $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
1625 - }
 1668+ // Return the new revision (or null) to the caller
 1669+ $status->value['revision'] = $revision;
16261670
 1671+ wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
 1672+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status ) );
 1673+
16271674 wfProfileOut( __METHOD__ );
1628 - return $revisionId;
 1675+ return $status;
16291676 }
16301677
16311678 /**
@@ -2590,7 +2637,12 @@
25912638 if( $bot && ($wgUser->isAllowed('markbotedits') || $wgUser->isAllowed('bot')) )
25922639 $flags |= EDIT_FORCE_BOT;
25932640 # Actually store the edit
2594 - $revId = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
 2641+ $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
 2642+ if ( !empty( $status->value['revision'] ) ) {
 2643+ $revId = $status->value['revision']->getId();
 2644+ } else {
 2645+ $revId = false;
 2646+ }
25952647
25962648 wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target ) );
25972649
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -1190,6 +1190,11 @@
11911191 'deleted-notice' => 'This page has been deleted.
11921192 The deletion log for the page is provided below for reference.',
11931193 'deletelog-fulllog' => 'View full log',
 1194+'edit-hook-aborted' => 'Edit aborted by hook, it gave no explanation.',
 1195+'edit-gone-missing' => 'Couldn\'t update the article, it appears to have been deleted.',
 1196+'edit-conflict' => 'Edit conflict.',
 1197+'edit-no-change' => 'Your edit was ignored, because no change was made to the text.',
 1198+'edit-already-exists' => 'Couldn\'t create a new article, it already exists.',
11941199
11951200 # Parser/template warnings
11961201 'expensive-parserfunction-warning' => 'Warning: This page contains too many expensive parser function calls.

Follow-up revisions

RevisionCommit summaryAuthorDate
r41245Updates for r41244, with backwards compatibility.tstarling10:17, 25 September 2008
r97149Per Nikerabbit on IRC, document the $status parameter in ArticleSaveComplete....catrope13:11, 15 September 2011
r97173Merged revisions 97087,97091-97092,97094,97096-97098,97100-97101,97103,97136,...dantman16:19, 15 September 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r41198* Make Article::doEdit() return the revId, not just true/false. Make the arti...aaron18:20, 23 September 2008