/** * @param Content $content Pre-save transform content * @param integer $flags * @param User $user * @param string $summary * @param array $meta * @return Status * @throws DBUnexpectedError * @throws Exception * @throws FatalError * @throws MWException */ private function doCreate(Content $content, $flags, User $user, $summary, array $meta) { global $wgUseRCPatrol, $wgUseNPPatrol; $status = Status::newGood(['new' => true, 'revision' => null]); $now = wfTimestampNow(); $newsize = $content->getSize(); $prepStatus = $content->prepareSave($this, $flags, $meta['oldId'], $user); $status->merge($prepStatus); if (!$status->isOK()) { return $status; } $dbw = wfGetDB(DB_MASTER); $dbw->startAtomic(__METHOD__); // Add the page record unless one already exists for the title $newid = $this->insertOn($dbw); if ($newid === false) { $dbw->endAtomic(__METHOD__); // nothing inserted $status->fatal('edit-already-exists'); return $status; // nothing done } // At this point we are now comitted to returning an OK // status unless some DB query error or other exception comes up. // This way callers don't have to call rollback() if $status is bad // unless they actually try to catch exceptions (which is rare). // @TODO: pass content object?! $revision = new Revision(['page' => $newid, 'title' => $this->mTitle, 'comment' => $summary, 'minor_edit' => $meta['minor'], 'text' => $meta['serialized'], 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $meta['serialFormat']]); // Save the revision text... $revisionId = $revision->insertOn($dbw); // Update the page record with revision data if (!$this->updateRevisionOn($dbw, $revision, 0)) { throw new MWException("Failed to update page row to use new revision."); } Hooks::run('NewRevisionFromEditComplete', [$this, $revision, false, $user]); // Update recentchanges if (!($flags & EDIT_SUPPRESS_RC)) { // Mark as patrolled if the user can do so $patrolled = ($wgUseRCPatrol || $wgUseNPPatrol) && !count($this->mTitle->getUserPermissionsErrors('autopatrol', $user)); // Add RC row to the DB RecentChange::notifyNew($now, $this->mTitle, $revision->isMinor(), $user, $summary, $meta['bot'], '', $newsize, $revisionId, $patrolled, $meta['tags']); } $user->incEditCount(); $dbw->endAtomic(__METHOD__); $this->mTimestamp = $now; // Return the new revision to the caller $status->value['revision'] = $revision; // Do secondary updates once the main changes have been committed... DeferredUpdates::addUpdate(new AtomicSectionUpdate($dbw, __METHOD__, function () use($revision, &$user, $content, $summary, &$flags, $meta, &$status) { // Update links, etc. $this->doEditUpdates($revision, $user, ['created' => true]); // Trigger post-create hook $params = [&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision]; ContentHandler::runLegacyHooks('ArticleInsertComplete', $params, '1.21'); Hooks::run('PageContentInsertComplete', $params); // Trigger post-save hook $params = array_merge($params, [&$status, $meta['baseRevId']]); ContentHandler::runLegacyHooks('ArticleSaveComplete', $params, '1.21'); Hooks::run('PageContentSaveComplete', $params); }), DeferredUpdates::PRESEND); return $status; }
/** * Change an existing article or create a new article. Updates RC and all necessary caches, * optionally via the deferred update array. * * @param $content Content: new content * @param string $summary edit summary * @param $flags Integer bitfield: * EDIT_NEW * Article is known or assumed to be non-existent, create a new one * EDIT_UPDATE * Article is known or assumed to be pre-existing, update it * EDIT_MINOR * Mark this edit minor, if the user is allowed to do so * EDIT_SUPPRESS_RC * Do not log the change in recentchanges * EDIT_FORCE_BOT * Mark the edit a "bot" edit regardless of user rights * EDIT_DEFER_UPDATES * Defer some of the updates until the end of index.php * EDIT_AUTOSUMMARY * Fill in blank summaries with generated text where possible * * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an * 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 bool|int $baseRevId the revision ID this edit was based off, if any * @param $user User the user doing the edit * @param $serialisation_format String: format for storing the content in the database * * @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 * edit-conflict: In update mode, the article changed unexpectedly * edit-no-change: Warning that the text was the same as before * edit-already-exists: In creation mode, but the article already exists * * Extensions may define additional errors. * * $return->value will contain an associative array with members as follows: * new: Boolean indicating if the function attempted to create a new article * revision: The revision object for the inserted revision, or null * * @since 1.21 */ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false, User $user = null, $serialisation_format = null ) { global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol; // Low-level sanity check if ( $this->mTitle->getText() === '' ) { throw new MWException( 'Something is trying to edit an article with an empty title' ); } wfProfileIn( __METHOD__ ); if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) { wfProfileOut( __METHOD__ ); return Status::newFatal( 'content-not-allowed-here', ContentHandler::getLocalizedName( $content->getModel() ), $this->getTitle()->getPrefixedText() ); } $user = is_null( $user ) ? $wgUser : $user; $status = Status::newGood( array() ); // 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 ); // handle hook $hook_args = array( &$this, &$user, &$content, &$summary, $flags & EDIT_MINOR, null, null, &$flags, &$status ); if ( !wfRunHooks( 'PageContentSave', $hook_args ) || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) { wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" ); if ( $status->isOK() ) { $status->fatal( 'edit-hook-aborted' ); } wfProfileOut( __METHOD__ ); return $status; } // Silently ignore EDIT_MINOR if not allowed $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' ); $bot = $flags & EDIT_FORCE_BOT; $old_content = $this->getContent( Revision::RAW ); // current revision's content $oldsize = $old_content ? $old_content->getSize() : 0; $oldid = $this->getLatest(); $oldIsRedirect = $this->isRedirect(); $oldcountable = $this->isCountable(); $handler = $content->getContentHandler(); // Provide autosummaries if one is not provided and autosummaries are enabled. if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) { if ( !$old_content ) { $old_content = null; } $summary = $handler->getAutosummary( $old_content, $content, $flags ); } $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format ); $serialized = $editInfo->pst; /** * @var Content $content */ $content = $editInfo->pstContent; $newsize = $content->getSize(); $dbw = wfGetDB( DB_MASTER ); $now = wfTimestampNow(); $this->mTimestamp = $now; if ( $flags & EDIT_UPDATE ) { // Update article, but only if changed. $status->value['new'] = false; if ( !$oldid ) { // Article gone missing wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" ); $status->fatal( 'edit-gone-missing' ); wfProfileOut( __METHOD__ ); return $status; } elseif ( !$old_content ) { // Sanity check for bug 37225 wfProfileOut( __METHOD__ ); throw new MWException( "Could not find text for current revision {$oldid}." ); } $revision = new Revision( array( 'page' => $this->getId(), 'title' => $this->getTitle(), // for determining the default content model 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'parent_id' => $oldid, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialisation_format, ) ); // XXX: pass content object?! $changed = !$content->equals( $old_content ); if ( $changed ) { if ( !$content->isValid() ) { wfProfileOut( __METHOD__ ); throw new MWException( "New content failed validity check!" ); } $dbw->begin( __METHOD__ ); $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user ); $status->merge( $prepStatus ); if ( !$status->isOK() ) { $dbw->rollback( __METHOD__ ); wfProfileOut( __METHOD__ ); return $status; } $revisionId = $revision->insertOn( $dbw ); // Update page // // Note that we use $this->mLatest instead of fetching a value from the master DB // during the course of this function. This makes sure that EditPage can detect // edit conflicts reliably, either by $ok here, or by $article->getTimestamp() // before this function is called. A previous function used a separate query, this // creates a window where concurrent edits can cause an ignored edit conflict. $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect ); if ( !$ok ) { // Belated edit conflict! Run away!! $status->fatal( 'edit-conflict' ); $dbw->rollback( __METHOD__ ); 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( __METHOD__ ); } else { // Bug 32948: revision ID must be set to page {{REVISIONID}} and // related variables correctly $revision->setId( $this->getLatest() ); } // Update links tables, site stats, etc. $this->doEditUpdates( $revision, $user, array( 'changed' => $changed, 'oldcountable' => $oldcountable ) ); if ( !$changed ) { $status->warning( 'edit-no-change' ); $revision = null; // Update page_touched, this is usually implicit in the page update // Other cache updates are done in onArticleEdit() $this->mTitle->invalidateCache(); } } else { // Create new article $status->value['new'] = true; $dbw->begin( __METHOD__ ); $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user ); $status->merge( $prepStatus ); if ( !$status->isOK() ) { $dbw->rollback( __METHOD__ ); wfProfileOut( __METHOD__ ); return $status; } $status->merge( $prepStatus ); // 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( __METHOD__ ); $status->fatal( 'edit-already-exists' ); wfProfileOut( __METHOD__ ); return $status; } // Save the revision text... $revision = new Revision( array( 'page' => $newid, 'title' => $this->getTitle(), // for determining the default content model 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialisation_format, ) ); $revisionId = $revision->insertOn( $dbw ); // Bug 37225: use accessor to get the text as Revision may trim it $content = $revision->getContent(); // sanity; get normalized version if ( $content ) { $newsize = $content->getSize(); } // Update the page record with revision data $this->updateRevisionOn( $dbw, $revision, 0 ); wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) ); // Update recentchanges if ( !( $flags & EDIT_SUPPRESS_RC ) ) { // Mark as patrolled if the user can do so $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count( $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); // Add RC row to the DB $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot, '', $newsize, $revisionId, $patrolled ); // Log auto-patrolled edits if ( $patrolled ) { PatrolLog::record( $rc, true, $user ); } } $user->incEditCount(); $dbw->commit( __METHOD__ ); // Update links, etc. $this->doEditUpdates( $revision, $user, array( 'created' => true ) ); $hook_args = array( &$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision ); ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args ); wfRunHooks( 'PageContentInsertComplete', $hook_args ); } // Do updates right now unless deferral was requested if ( !( $flags & EDIT_DEFER_UPDATES ) ) { DeferredUpdates::doUpdates(); } // Return the new revision (or null) to the caller $status->value['revision'] = $revision; $hook_args = array( &$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ); ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args ); wfRunHooks( 'PageContentSaveComplete', $hook_args ); // Promote user to any groups they meet the criteria for $user->addAutopromoteOnceGroups( 'onEdit' ); wfProfileOut( __METHOD__ ); return $status; }
/** * Article::doEdit() * * Change an existing article or create a new article. Updates RC and all necessary caches, * optionally via the deferred update array. * * $wgUser must be set before calling this function. * * @param $text String: new text * @param $summary String: edit summary * @param $flags Integer bitfield: * EDIT_NEW * Article is known or assumed to be non-existent, create a new one * EDIT_UPDATE * Article is known or assumed to be pre-existing, update it * EDIT_MINOR * Mark this edit minor, if the user is allowed to do so * EDIT_SUPPRESS_RC * Do not log the change in recentchanges * EDIT_FORCE_BOT * Mark the edit a "bot" edit regardless of user rights * EDIT_DEFER_UPDATES * Defer some of the updates until the end of index.php * EDIT_AUTOSUMMARY * Fill in blank summaries with generated text where possible * * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. * If EDIT_UPDATE is specified and the article doesn't exist, the function will an * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an * 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 $user Optional user object, $wgUser will be used if not passed * * @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 * edit-conflict: In update mode, the article changed unexpectedly * edit-no-change: Warning that the text was the same as before * edit-already-exists: In creation mode, but the article already exists * * Extensions may define additional errors. * * $return->value will contain an associative array with members as follows: * new: Boolean indicating if the function attempted to create a new article * revision: The revision object for the inserted revision, or null * * 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; # Low-level sanity check if ($this->mTitle->getText() === '') { throw new MWException('Something is trying to edit an article with an empty title'); } wfProfileIn(__METHOD__); $user = is_null($user) ? $wgUser : $user; $status = Status::newGood(array()); # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already $this->loadPageData(); $flags = $this->checkFlags($flags); if (!wfRunHooks('ArticleSave', array(&$this, &$user, &$text, &$summary, $flags & EDIT_MINOR, null, null, &$flags, &$status))) { wfDebug(__METHOD__ . ": ArticleSave hook aborted save!\n"); if ($status->isOK()) { $status->fatal('edit-hook-aborted'); } wfProfileOut(__METHOD__); return $status; } # Silently ignore EDIT_MINOR if not allowed $isminor = $flags & EDIT_MINOR && $user->isAllowed('minoredit'); $bot = $flags & EDIT_FORCE_BOT; $oldtext = $this->getRawText(); // current revision $oldsize = strlen($oldtext); # Provide autosummaries if one is not provided and autosummaries are enabled. if ($wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '') { $summary = $this->getAutosummary($oldtext, $text, $flags); } $editInfo = $this->prepareTextForEdit($text); $text = $editInfo->pst; $newsize = strlen($text); $dbw = wfGetDB(DB_MASTER); $now = wfTimestampNow(); $this->mTimestamp = $now; if ($flags & EDIT_UPDATE) { # Update article, but only if changed. $status->value['new'] = false; # Make sure the revision is either completely inserted or not inserted at all if (!$wgDBtransactions) { $userAbort = ignore_user_abort(true); } $changed = strcmp($text, $oldtext) != 0; if ($changed) { $this->mGoodAdjustment = (int) $this->isCountable($text) - (int) $this->isCountable($oldtext); $this->mTotalAdjustment = 0; if (!$this->mLatest) { # Article gone missing wfDebug(__METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n"); $status->fatal('edit-gone-missing'); wfProfileOut(__METHOD__); return $status; } $revision = new Revision(array('page' => $this->getId(), 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $text, 'parent_id' => $this->mLatest, 'user' => $user->getId(), 'user_text' => $user->getName())); $dbw->begin(); $revisionId = $revision->insertOn($dbw); # Update page # # Note that we use $this->mLatest instead of fetching a value from the master DB # during the course of this function. This makes sure that EditPage can detect # edit conflicts reliably, either by $ok here, or by $article->getTimestamp() # before this function is called. A previous function used a separate query, this # creates a window where concurrent edits can cause an ignored edit conflict. $ok = $this->updateRevisionOn($dbw, $revision, $this->mLatest); if (!$ok) { /* 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__); } $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 && $this->mTitle->userCan('autopatrol'); # Add RC row to the DB $rc = RecentChange::notifyEdit($now, $this->mTitle, $isminor, $user, $summary, $this->mLatest, $this->getTimestamp(), $bot, '', $oldsize, $newsize, $revisionId, $patrolled); # Log auto-patrolled edits if ($patrolled) { PatrolLog::record($rc, true); } } $user->incEditCount(); $dbw->commit(); } } else { $status->warning('edit-no-change'); $revision = null; // Keep the same revision ID, but do some updates on it $revisionId = $this->getRevIdFetched(); // Update page_touched, this is usually implicit in the page update // Other cache updates are done in onArticleEdit() $this->mTitle->invalidateCache(); } 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; } # Invalidate cache of this article and all pages using this article # as a template. Partly deferred. Article::onArticleEdit($this->mTitle); # Update links tables, site stats, etc. $this->editUpdates($text, $summary, $isminor, $now, $revisionId, $changed); } else { # Create new article $status->value['new'] = true; # Set statistics members # We work out if it's countable after PST to avoid counter drift # when articles are created with {{subst:}} $this->mGoodAdjustment = (int) $this->isCountable($text); $this->mTotalAdjustment = 1; $dbw->begin(); # 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(); $status->fatal('edit-already-exists'); wfProfileOut(__METHOD__); return $status; } # Save the revision text... $revision = new Revision(array('page' => $newid, 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $text, 'user' => $user->getId(), 'user_text' => $user->getName())); $revisionId = $revision->insertOn($dbw); $this->mTitle->resetArticleID($newid); # Update the page record with revision data $this->updateRevisionOn($dbw, $revision, 0); wfRunHooks('NewRevisionFromEditComplete', array($this, $revision, false, $user)); # Update recentchanges if (!($flags & EDIT_SUPPRESS_RC)) { global $wgUseRCPatrol, $wgUseNPPatrol; # Mark as patrolled if the user can do so $patrolled = ($wgUseRCPatrol || $wgUseNPPatrol) && $this->mTitle->userCan('autopatrol'); # Add RC row to the DB $rc = RecentChange::notifyNew($now, $this->mTitle, $isminor, $user, $summary, $bot, '', strlen($text), $revisionId, $patrolled); # Log auto-patrolled edits if ($patrolled) { PatrolLog::record($rc, true); } } $user->incEditCount(); $dbw->commit(); # Update links, etc. $this->editUpdates($text, $summary, $isminor, $now, $revisionId, true); # Clear caches Article::onArticleCreate($this->mTitle); wfRunHooks('ArticleInsertComplete', array(&$this, &$user, $text, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision)); } # Do updates right now unless deferral was requested if (!($flags & EDIT_DEFER_UPDATES)) { wfDoUpdates(); } // Return the new revision (or null) to the caller $status->value['revision'] = $revision; wfRunHooks('ArticleSaveComplete', array(&$this, &$user, $text, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId)); wfProfileOut(__METHOD__); return $status; }
function insertNewArticle($article, $text, $summary, $isminor, $watchthis, $suppressRC = false, $comment = false) { global $wgOut, $wgUser; global $wgUseSquid, $wgDeferredUpdateList, $wgInternalServer; $fname = 'Article::insertNewArticle'; wfProfileIn($fname); $article->mGoodAdjustment = $article->isCountable($text); $article->mTotalAdjustment = 1; $ns = $article->mTitle->getNamespace(); $ttl = $article->mTitle->getDBkey(); # If this is a comment, add the summary as headline if ($comment && $summary != "") { $text = "== {$summary} ==\n\n" . $text; } $text = $article->preSaveTransform($text); $isminor = $isminor && $wgUser->isLoggedIn() ? 1 : 0; $now = wfTimestampNow(); $dbw =& wfGetDB(DB_MASTER); # Add the page record; stake our claim on this title! $newid = $article->insertOn($dbw); # Save the revision text... $revision = new Revision(array('page' => $newid, 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $text)); $revisionId = $revision->insertOn($dbw); $article->mTitle->resetArticleID($newid); # Update the page record with revision data $article->updateRevisionOn($dbw, $revision, 0); Article::onArticleCreate($article->mTitle); if (!$suppressRC) { RecentChange::notifyNew($now, $article->mTitle, $isminor, $wgUser, $summary, 'default', '', strlen($text), $revisionId); } if ($watchthis) { #if(!$article->mTitle->userIsWatching()) $this->watch($article); if (wfRunHooks('WatchArticle', array(&$wgUser, &$article))) { $wgUser->addWatch($article->mTitle); $wgUser->saveSettings(); wfRunHooks('WatchArticleComplete', array(&$wgUser, &$article)); } } # The talk page isn't in the regular link tables, so we need to update manually: $talkns = $ns ^ 1; # talk -> normal; normal -> talk $dbw->update('page', array('page_touched' => $dbw->timestamp($now)), array('page_namespace' => $talkns, 'page_title' => $ttl), $fname); # standard deferred updates $article->editUpdates($text, $summary, $isminor, $now, $revisionId); if ($this->mprotect == "yes") { # $this->mprotect # $this->mprotect_reason $id = $article->mTitle->getArticleID(); $limit = 'sysop'; $dbw->update('page', array('page_touched' => $dbw->timestamp(), 'page_restrictions' => (string) $limit), array('page_id' => $id), 'Article::protect'); $restrictions = "move=" . $limit; $restrictions .= ":edit=" . $limit; if (!$moveonly) { #$restrictions .= ":edit=" . $limit; } if (wfRunHooks('ArticleProtect', array(&$article, &$wgUser, $limit == 'sysop', $this->mprotect_reason, $moveonly))) { $dbw =& wfGetDB(DB_MASTER); $dbw->update('page', array('page_touched' => $dbw->timestamp(), 'page_restrictions' => $restrictions), array('page_id' => $id), 'Article::protect'); wfRunHooks('ArticleProtectComplete', array(&$article, &$wgUser, $limit == 'sysop', $this->mprotect_reason, $moveonly)); $log = new LogPage('protect'); $log->addEntry('protect', $article->mTitle, $this->mprotect_reason); } } # AWC - Edit # Dont want ot redirect... #$oldid = 0; # new article #$article->showArticle( $text, wfMsg( 'newarticle' ), false, $isminor, $now, $summary, $oldid ); wfProfileOut($fname); }
/** * Article::doEdit() * * Change an existing article or create a new article. Updates RC and all necessary caches, * optionally via the deferred update array. * * $wgUser must be set before calling this function. * * @param string $text New text * @param string $summary Edit summary * @param integer $flags bitfield: * EDIT_NEW * Article is known or assumed to be non-existent, create a new one * EDIT_UPDATE * Article is known or assumed to be pre-existing, update it * EDIT_MINOR * Mark this edit minor, if the user is allowed to do so * EDIT_SUPPRESS_RC * Do not log the change in recentchanges * EDIT_FORCE_BOT * Mark the edit a "bot" edit regardless of user rights * EDIT_DEFER_UPDATES * Defer some of the updates until the end of index.php * EDIT_AUTOSUMMARY * Fill in blank summaries with generated text where possible * * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If * EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception * to be thrown from the Database. These two conditions are also possible with auto-detection due * to MediaWiki's performance-optimised locking strategy. * * @return bool success */ function doEdit($text, $summary, $flags = 0) { global $wgUser, $wgDBtransactions; wfProfileIn(__METHOD__); $good = true; if (!($flags & EDIT_NEW) && !($flags & EDIT_UPDATE)) { $aid = $this->mTitle->getArticleID(GAID_FOR_UPDATE); if ($aid) { $flags |= EDIT_UPDATE; } else { $flags |= EDIT_NEW; } } if (!wfRunHooks('ArticleSave', array(&$this, &$wgUser, &$text, &$summary, $flags & EDIT_MINOR, null, null, &$flags))) { wfDebug(__METHOD__ . ": ArticleSave hook aborted save!\n"); wfProfileOut(__METHOD__); return false; } # Silently ignore EDIT_MINOR if not allowed $isminor = $flags & EDIT_MINOR && $wgUser->isAllowed('minoredit'); $bot = $wgUser->isAllowed('bot') || $flags & EDIT_FORCE_BOT; $oldtext = $this->getContent(); $oldsize = strlen($oldtext); # Provide autosummaries if one is not provided. if ($flags & EDIT_AUTOSUMMARY && $summary == '') { $summary = $this->getAutosummary($oldtext, $text, $flags); } $text = $this->preSaveTransform($text); $newsize = strlen($text); $dbw =& wfGetDB(DB_MASTER); $now = wfTimestampNow(); if ($flags & EDIT_UPDATE) { # Update article, but only if changed. # Make sure the revision is either completely inserted or not inserted at all if (!$wgDBtransactions) { $userAbort = ignore_user_abort(true); } $lastRevision = 0; $revisionId = 0; if (0 != strcmp($text, $oldtext)) { $this->mGoodAdjustment = (int) $this->isCountable($text) - (int) $this->isCountable($oldtext); $this->mTotalAdjustment = 0; $lastRevision = $dbw->selectField('page', 'page_latest', array('page_id' => $this->getId())); if (!$lastRevision) { # Article gone missing wfDebug(__METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n"); wfProfileOut(__METHOD__); return false; } $revision = new Revision(array('page' => $this->getId(), 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $text)); $dbw->begin(); $revisionId = $revision->insertOn($dbw); # Update page $ok = $this->updateRevisionOn($dbw, $revision, $lastRevision); if (!$ok) { /* Belated edit conflict! Run away!! */ $good = false; $dbw->rollback(); } else { # Update recentchanges if (!($flags & EDIT_SUPPRESS_RC)) { $rcid = RecentChange::notifyEdit($now, $this->mTitle, $isminor, $wgUser, $summary, $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize, $revisionId); # Mark as patrolled if the user can do so if ($wgUser->isAllowed('autopatrol')) { RecentChange::markPatrolled($rcid); } } $wgUser->incEditCount(); $dbw->commit(); } } else { // Keep the same revision ID, but do some updates on it $revisionId = $this->getRevIdFetched(); // Update page_touched, this is usually implicit in the page update // Other cache updates are done in onArticleEdit() $this->mTitle->invalidateCache(); } if (!$wgDBtransactions) { ignore_user_abort($userAbort); } if ($good) { # Invalidate cache of this article and all pages using this article # as a template. Partly deferred. Article::onArticleEdit($this->mTitle); # Update links tables, site stats, etc. $changed = strcmp($oldtext, $text) != 0; $this->editUpdates($text, $summary, $isminor, $now, $revisionId, $changed); } } else { # Create new article # Set statistics members # We work out if it's countable after PST to avoid counter drift # when articles are created with {{subst:}} $this->mGoodAdjustment = (int) $this->isCountable($text); $this->mTotalAdjustment = 1; $dbw->begin(); # Add the page record; stake our claim on this title! # This will fail with a database query exception if the article already exists $newid = $this->insertOn($dbw); # Save the revision text... $revision = new Revision(array('page' => $newid, 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $text)); $revisionId = $revision->insertOn($dbw); $this->mTitle->resetArticleID($newid); # Update the page record with revision data $this->updateRevisionOn($dbw, $revision, 0); if (!($flags & EDIT_SUPPRESS_RC)) { $rcid = RecentChange::notifyNew($now, $this->mTitle, $isminor, $wgUser, $summary, $bot, '', strlen($text), $revisionId); # Mark as patrolled if the user can if ($wgUser->isAllowed('autopatrol')) { RecentChange::markPatrolled($rcid); } } $wgUser->incEditCount(); $dbw->commit(); # Update links, etc. $this->editUpdates($text, $summary, $isminor, $now, $revisionId, true); # Clear caches Article::onArticleCreate($this->mTitle); wfRunHooks('ArticleInsertComplete', array(&$this, &$wgUser, $text, $summary, $flags & EDIT_MINOR, null, null, &$flags)); } if ($good && !($flags & EDIT_DEFER_UPDATES)) { wfDoUpdates(); } wfRunHooks('ArticleSaveComplete', array(&$this, &$wgUser, $text, $summary, $flags & EDIT_MINOR, null, null, &$flags)); wfProfileOut(__METHOD__); return $good; }
public function execute() { $userName = $this->getOption('user', false); $summary = $this->getOption('summary', 'Imported from text file'); $useTimestamp = $this->hasOption('use-timestamp'); $rc = $this->hasOption('rc'); $bot = $this->hasOption('bot'); $overwrite = $this->hasOption('overwrite'); $prefix = $this->getOption('prefix', ''); // Get all the arguments. A loop is required since Maintenance doesn't // support an arbitrary number of arguments. $files = []; $i = 0; while ($arg = $this->getArg($i++)) { if (file_exists($arg)) { $files[$arg] = file_get_contents($arg); } else { // use glob to support the Windows shell, which doesn't automatically // expand wildcards $found = false; foreach (glob($arg) as $filename) { $found = true; $files[$filename] = file_get_contents($filename); } if (!$found) { $this->error("Fatal error: The file '{$arg}' does not exist!", 1); } } } $count = count($files); $this->output("Importing {$count} pages...\n"); if ($userName === false) { $user = User::newSystemUser('Maintenance script', ['steal' => true]); } else { $user = User::newFromName($userName); } if (!$user) { $this->error("Invalid username\n", true); } if ($user->isAnon()) { $user->addToDatabase(); } $exit = 0; $successCount = 0; $failCount = 0; $skipCount = 0; foreach ($files as $file => $text) { $pageName = $prefix . pathinfo($file, PATHINFO_FILENAME); $timestamp = $useTimestamp ? wfTimestamp(TS_UNIX, filemtime($file)) : wfTimestampNow(); $title = Title::newFromText($pageName); // Have to check for # manually, since it gets interpreted as a fragment if (!$title || $title->hasFragment()) { $this->error("Invalid title {$pageName}. Skipping.\n"); $skipCount++; continue; } $exists = $title->exists(); $oldRevID = $title->getLatestRevID(); $oldRev = $oldRevID ? Revision::newFromId($oldRevID) : null; $actualTitle = $title->getPrefixedText(); if ($exists) { $touched = wfTimestamp(TS_UNIX, $title->getTouched()); if (!$overwrite) { $this->output("Title {$actualTitle} already exists. Skipping.\n"); $skipCount++; continue; } elseif ($useTimestamp && intval($touched) >= intval($timestamp)) { $this->output("File for title {$actualTitle} has not been modified since the " . "destination page was touched. Skipping.\n"); $skipCount++; continue; } } $rev = new WikiRevision(ConfigFactory::getDefaultInstance()->makeConfig('main')); $rev->setText(rtrim($text)); $rev->setTitle($title); $rev->setUserObj($user); $rev->setComment($summary); $rev->setTimestamp($timestamp); if ($exists && $overwrite && $rev->getContent()->equals($oldRev->getContent())) { $this->output("File for title {$actualTitle} contains no changes from the current " . "revision. Skipping.\n"); $skipCount++; continue; } $status = $rev->importOldRevision(); $newId = $title->getLatestRevID(); if ($status) { $action = $exists ? 'updated' : 'created'; $this->output("Successfully {$action} {$actualTitle}\n"); $successCount++; } else { $action = $exists ? 'update' : 'create'; $this->output("Failed to {$action} {$actualTitle}\n"); $failCount++; $exit = 1; } // Create the RecentChanges entry if necessary if ($rc && $status) { if ($exists) { if (is_object($oldRev)) { $oldContent = $oldRev->getContent(); RecentChange::notifyEdit($timestamp, $title, $rev->getMinor(), $user, $summary, $oldRevID, $oldRev->getTimestamp(), $bot, '', $oldContent ? $oldContent->getSize() : 0, $rev->getContent()->getSize(), $newId, 1); } } else { RecentChange::notifyNew($timestamp, $title, $rev->getMinor(), $user, $summary, $bot, '', $rev->getContent()->getSize(), $newId, 1); } } } $this->output("Done! {$successCount} succeeded, {$skipCount} skipped.\n"); if ($exit) { $this->error("Import failed with {$failCount} failed pages.\n", $exit); } }
/** * Change an existing article or create a new article. Updates RC and all necessary caches, * optionally via the deferred update array. * * @param Content $content New content * @param string $summary Edit summary * @param int $flags Bitfield: * EDIT_NEW * Article is known or assumed to be non-existent, create a new one * EDIT_UPDATE * Article is known or assumed to be pre-existing, update it * EDIT_MINOR * Mark this edit minor, if the user is allowed to do so * EDIT_SUPPRESS_RC * Do not log the change in recentchanges * EDIT_FORCE_BOT * Mark the edit a "bot" edit regardless of user rights * EDIT_AUTOSUMMARY * Fill in blank summaries with generated text where possible * * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the * article will be detected. If EDIT_UPDATE is specified and the article * doesn't exist, the function will return an edit-gone-missing error. If * EDIT_NEW is specified and the article does exist, an 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 bool|int $baseRevId The revision ID this edit was based off, if any. * This is not the parent revision ID, rather the revision ID for older * content used as the source for a rollback, for example. * @param User $user The user doing the edit * @param string $serialFormat Format for storing the content in the * database. * * @throws MWException * @return Status 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. * edit-conflict: In update mode, the article changed unexpectedly. * edit-no-change: Warning that the text was the same as before. * edit-already-exists: In creation mode, but the article already exists. * * Extensions may define additional errors. * * $return->value will contain an associative array with members as follows: * new: Boolean indicating if the function attempted to create a new article. * revision: The revision object for the inserted revision, or null. * * @since 1.21 * @throws MWException */ public function doEditContent(Content $content, $summary, $flags = 0, $baseRevId = false, User $user = null, $serialFormat = null) { global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol; // Low-level sanity check if ($this->mTitle->getText() === '') { throw new MWException('Something is trying to edit an article with an empty title'); } if (!$content->getContentHandler()->canBeUsedOn($this->getTitle())) { return Status::newFatal('content-not-allowed-here', ContentHandler::getLocalizedName($content->getModel()), $this->getTitle()->getPrefixedText()); } $user = is_null($user) ? $wgUser : $user; $status = Status::newGood(array()); // 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); // handle hook $hook_args = array(&$this, &$user, &$content, &$summary, $flags & EDIT_MINOR, null, null, &$flags, &$status); if (!Hooks::run('PageContentSave', $hook_args) || !ContentHandler::runLegacyHooks('ArticleSave', $hook_args)) { wfDebug(__METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n"); if ($status->isOK()) { $status->fatal('edit-hook-aborted'); } return $status; } // Silently ignore EDIT_MINOR if not allowed $isminor = $flags & EDIT_MINOR && $user->isAllowed('minoredit'); $bot = $flags & EDIT_FORCE_BOT; $old_revision = $this->getRevision(); // current revision $old_content = $this->getContent(Revision::RAW); // current revision's content $oldsize = $old_content ? $old_content->getSize() : 0; $oldid = $this->getLatest(); $oldIsRedirect = $this->isRedirect(); $oldcountable = $this->isCountable(); $handler = $content->getContentHandler(); // Provide autosummaries if one is not provided and autosummaries are enabled. if ($wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '') { if (!$old_content) { $old_content = null; } $summary = $handler->getAutosummary($old_content, $content, $flags); } $editInfo = $this->prepareContentForEdit($content, null, $user, $serialFormat); $serialized = $editInfo->pst; /** * @var Content $content */ $content = $editInfo->pstContent; $newsize = $content->getSize(); $dbw = wfGetDB(DB_MASTER); $now = wfTimestampNow(); if ($flags & EDIT_UPDATE) { // Update article, but only if changed. $status->value['new'] = false; if (!$oldid) { // Article gone missing wfDebug(__METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n"); $status->fatal('edit-gone-missing'); return $status; } elseif (!$old_content) { // Sanity check for bug 37225 throw new MWException("Could not find text for current revision {$oldid}."); } $revision = new Revision(array('page' => $this->getId(), 'title' => $this->getTitle(), 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'parent_id' => $oldid, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialFormat)); // XXX: pass content object?! $changed = !$content->equals($old_content); if ($changed) { $prepStatus = $content->prepareSave($this, $flags, $oldid, $user); $status->merge($prepStatus); if (!$status->isOK()) { return $status; } $dbw->begin(__METHOD__); // Get the latest page_latest value while locking it. // Do a CAS style check to see if it's the same as when this method // started. If it changed then bail out before touching the DB. $latestNow = $this->lockAndGetLatest(); if ($latestNow != $oldid) { $dbw->commit(__METHOD__); // Page updated or deleted in the mean time $status->fatal('edit-conflict'); return $status; } // At this point we are now comitted to returning an OK // status unless some DB query error or other exception comes up. // This way callers don't have to call rollback() if $status is bad // unless they actually try to catch exceptions (which is rare). $revisionId = $revision->insertOn($dbw); // Update page_latest and friends to reflect the new revision if (!$this->updateRevisionOn($dbw, $revision, null, $oldIsRedirect)) { $dbw->rollback(__METHOD__); throw new MWException("Failed to update page row to use new revision."); } Hooks::run('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 RecentChange::notifyEdit($now, $this->mTitle, $isminor, $user, $summary, $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize, $revisionId, $patrolled); } $user->incEditCount(); $dbw->commit(__METHOD__); $this->mTimestamp = $now; } else { // Bug 32948: revision ID must be set to page {{REVISIONID}} and // related variables correctly $revision->setId($this->getLatest()); } // Update links tables, site stats, etc. $this->doEditUpdates($revision, $user, array('changed' => $changed, 'oldcountable' => $oldcountable, 'oldrevision' => $old_revision)); if (!$changed) { $status->warning('edit-no-change'); $revision = null; // Update page_touched, this is usually implicit in the page update // Other cache updates are done in onArticleEdit() $this->mTitle->invalidateCache($now); } } else { // Create new article $status->value['new'] = true; $prepStatus = $content->prepareSave($this, $flags, $oldid, $user); $status->merge($prepStatus); if (!$status->isOK()) { return $status; } $dbw->begin(__METHOD__); // Add the page record unless one already exists for the title $newid = $this->insertOn($dbw); if ($newid === false) { $dbw->commit(__METHOD__); // nothing inserted $status->fatal('edit-already-exists'); return $status; // nothing done } // At this point we are now comitted to returning an OK // status unless some DB query error or other exception comes up. // This way callers don't have to call rollback() if $status is bad // unless they actually try to catch exceptions (which is rare). // Save the revision text... $revision = new Revision(array('page' => $newid, 'title' => $this->getTitle(), 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialFormat)); $revisionId = $revision->insertOn($dbw); // Bug 37225: use accessor to get the text as Revision may trim it $content = $revision->getContent(); // sanity; get normalized version if ($content) { $newsize = $content->getSize(); } // Update the page record with revision data if (!$this->updateRevisionOn($dbw, $revision, 0)) { $dbw->rollback(__METHOD__); throw new MWException("Failed to update page row to use new revision."); } Hooks::run('NewRevisionFromEditComplete', array($this, $revision, false, $user)); // Update recentchanges if (!($flags & EDIT_SUPPRESS_RC)) { // Mark as patrolled if the user can do so $patrolled = ($wgUseRCPatrol || $wgUseNPPatrol) && !count($this->mTitle->getUserPermissionsErrors('autopatrol', $user)); // Add RC row to the DB RecentChange::notifyNew($now, $this->mTitle, $isminor, $user, $summary, $bot, '', $newsize, $revisionId, $patrolled); } $user->incEditCount(); $dbw->commit(__METHOD__); $this->mTimestamp = $now; // Update links, etc. $this->doEditUpdates($revision, $user, array('created' => true, 'oldrevision' => $old_revision)); $hook_args = array(&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision); ContentHandler::runLegacyHooks('ArticleInsertComplete', $hook_args); Hooks::run('PageContentInsertComplete', $hook_args); } // Return the new revision (or null) to the caller $status->value['revision'] = $revision; $hook_args = array(&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId); ContentHandler::runLegacyHooks('ArticleSaveComplete', $hook_args); Hooks::run('PageContentSaveComplete', $hook_args); // Promote user to any groups they meet the criteria for DeferredUpdates::addCallableUpdate(function () use($user) { $user->addAutopromoteOnceGroups('onEdit'); $user->addAutopromoteOnceGroups('onView'); // b/c }); return $status; }
/** * Theoretically we could defer these whole insert and update * functions for after display, but that's taking a big leap * of faith, and we want to be able to report database * errors at some point. * @private */ function insertNewArticle($text, $summary, $isminor, $watchthis, $suppressRC = false, $comment = false) { global $wgOut, $wgUser; global $wgUseSquid, $wgDeferredUpdateList, $wgInternalServer; $fname = 'Article::insertNewArticle'; wfProfileIn($fname); $this->mGoodAdjustment = $this->isCountable($text); $this->mTotalAdjustment = 1; $ns = $this->mTitle->getNamespace(); $ttl = $this->mTitle->getDBkey(); # If this is a comment, add the summary as headline if ($comment && $summary != "") { $text = "== {$summary} ==\n\n" . $text; } $text = $this->preSaveTransform($text); $isminor = $isminor && $wgUser->isLoggedIn() ? 1 : 0; $now = wfTimestampNow(); $dbw =& wfGetDB(DB_MASTER); # Add the page record; stake our claim on this title! $newid = $this->insertOn($dbw); # Save the revision text... $revision = new Revision(array('page' => $newid, 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $text)); $revisionId = $revision->insertOn($dbw); $this->mTitle->resetArticleID($newid); # Update the page record with revision data $this->updateRevisionOn($dbw, $revision, 0); Article::onArticleCreate($this->mTitle); if ($watchthis) { if (!$this->mTitle->userIsWatching()) { $this->watch(); } } else { if ($this->mTitle->userIsWatching()) { $this->unwatch(); } } # The talk page isn't in the regular link tables, so we need to update manually: $talkns = $ns ^ 1; # talk -> normal; normal -> talk $dbw->update('page', array('page_touched' => $dbw->timestamp($now)), array('page_namespace' => $talkns, 'page_title' => $ttl), $fname); # standard deferred updates $this->editUpdates($text); # TG PATCH. Moved from some lines above to here # Is important to call RecentChange::notifyNew _after_ editUpdates # because a user_newtalk flag and addwatch might be committed in editUpdates # which triggers the sending of an Enotif in RecentChange::notifyNew just right now if (!$suppressRC) { RecentChange::notifyNew($now, $this->mTitle, $isminor, $wgUser, $summary, 'default', '', strlen($text), $revisionId); } $oldid = 0; # new article $this->showArticle($text, wfMsg('newarticle'), false); wfProfileOut($fname); }
/** * Duplicate one page to another, including full histories * Does some basic error-catching, but not as much as the code above [should] * * @param $source Title to duplicate * @param $dest Title to save to * @return bool */ private function duplicate(&$source, &$dest) { global $wgUser, $wgBot; if (!$source->exists() || $dest->exists()) { return false; } # Source doesn't exist, or destination does $dbw = wfGetDB(DB_MASTER); $dbw->begin(); $sid = $source->getArticleId(); # Create an article representing the destination page and save it $destArticle = new Article($dest); $aid = $destArticle->insertOn($dbw); # Perform the revision duplication # An INSERT...SELECT here seems to f**k things up $res = $dbw->select('revision', '*', array('rev_page' => $sid), __METHOD__); if ($res && $dbw->numRows($res) > 0) { while ($row = $dbw->fetchObject($res)) { $values['rev_page'] = $aid; $values['rev_text_id'] = $row->rev_text_id; $values['rev_comment'] = $row->rev_comment; $values['rev_user'] = $row->rev_user; $values['rev_user_text'] = $row->rev_user_text; $values['rev_timestamp'] = $row->rev_timestamp; $values['rev_minor_edit'] = $row->rev_minor_edit; $values['rev_deleted'] = $row->rev_deleted; $dbw->insert('revision', $values, __METHOD__); } $dbw->freeResult($res); } # Update page record $latest = $dbw->selectField('revision', 'MAX(rev_id)', array('rev_page' => $aid), __METHOD__); $rev = Revision::newFromId($latest); $destArticle->updateRevisionOn($dbw, $rev); # Commit transaction $dbw->commit(); # Create a null revision with an explanation; do cache clearances, etc. $dbw->begin(); $comment = wfMsgForContent('duplicator-summary', $source->getPrefixedText()); $nr = Revision::newNullRevision($dbw, $aid, $comment, true); $nid = $nr->insertOn($dbw); $destArticle->updateRevisionOn($dbw, $nr); $destArticle->createUpdates($nr); Article::onArticleCreate($dest); $bot = $wgUser->isAllowed('bot'); RecentChange::notifyNew($nr->getTimestamp(), $dest, true, $wgUser, $comment, $bot); $dest->invalidateCache(); $dbw->commit(); return true; }