/** * Generate the navigation links when browsing through an article revisions * It shows the information as: * Revision as of \<date\>; view current revision * \<- Previous version | Next Version -\> * * @param $oldid String: revision ID of this article revision */ public function setOldSubtitle($oldid = 0) { global $wgLang, $wgOut, $wgUser, $wgRequest; if (!wfRunHooks('DisplayOldSubtitle', array(&$this, &$oldid))) { return; } $unhide = $wgRequest->getInt('unhide') == 1; # Cascade unhide param in links for easy deletion browsing $extraParams = array(); if ($wgRequest->getVal('unhide')) { $extraParams['unhide'] = 1; } $revision = Revision::newFromId($oldid); $timestamp = $revision->getTimestamp(); $current = $oldid == $this->mPage->getLatest(); $td = $wgLang->timeanddate($timestamp, true); $tddate = $wgLang->date($timestamp, true); $tdtime = $wgLang->time($timestamp, true); # Show user links if allowed to see them. If hidden, then show them only if requested... $userlinks = Linker::revUserTools($revision, !$unhide); $infomsg = $current && !wfMessage('revision-info-current')->isDisabled() ? 'revision-info-current' : 'revision-info'; $wgOut->addSubtitle("<div id=\"mw-{$infomsg}\">" . wfMessage($infomsg, $td)->rawParams($userlinks)->params($revision->getID(), $tddate, $tdtime, $revision->getUser())->parse() . "</div>"); $lnk = $current ? wfMsgHtml('currentrevisionlink') : Linker::link($this->getTitle(), wfMsgHtml('currentrevisionlink'), array(), $extraParams, array('known', 'noclasses')); $curdiff = $current ? wfMsgHtml('diff') : Linker::link($this->getTitle(), wfMsgHtml('diff'), array(), array('diff' => 'cur', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')); $prev = $this->getTitle()->getPreviousRevisionID($oldid); $prevlink = $prev ? Linker::link($this->getTitle(), wfMsgHtml('previousrevision'), array(), array('direction' => 'prev', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')) : wfMsgHtml('previousrevision'); $prevdiff = $prev ? Linker::link($this->getTitle(), wfMsgHtml('diff'), array(), array('diff' => 'prev', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')) : wfMsgHtml('diff'); $nextlink = $current ? wfMsgHtml('nextrevision') : Linker::link($this->getTitle(), wfMsgHtml('nextrevision'), array(), array('direction' => 'next', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')); $nextdiff = $current ? wfMsgHtml('diff') : Linker::link($this->getTitle(), wfMsgHtml('diff'), array(), array('diff' => 'next', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')); $cdel = Linker::getRevDeleteLink($wgUser, $revision, $this->getTitle()); if ($cdel !== '') { $cdel .= ' '; } $wgOut->addSubtitle("<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt('revision-nav', array('escapenoentities', 'parsemag', 'replaceafter'), $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff) . "</div>"); }
/** * @return bool */ public function doWork() { global $wgUseFileCache; // @todo several of the methods called on $this->page are not declared in Page, but present // in WikiPage and delegated by Article. $isCurrent = $this->revid === $this->page->getLatest(); if ($this->content !== null) { $content = $this->content; } elseif ($isCurrent) { // XXX: why use RAW audience here, and PUBLIC (default) below? $content = $this->page->getContent(Revision::RAW); } else { $rev = Revision::newFromTitle($this->page->getTitle(), $this->revid); if ($rev === null) { $content = null; } else { // XXX: why use PUBLIC audience here (default), and RAW above? $content = $rev->getContent(); } } if ($content === null) { return false; } // Reduce effects of race conditions for slow parses (bug 46014) $cacheTime = wfTimestampNow(); $time = -microtime(true); $this->parserOutput = $content->getParserOutput($this->page->getTitle(), $this->revid, $this->parserOptions); $time += microtime(true); // Timing hack if ($time > 3) { // TODO: Use Parser's logger (once it has one) $logger = MediaWiki\Logger\LoggerFactory::getInstance('slow-parse'); $logger->info('{time} {title}', ['time' => number_format($time, 2), 'title' => $this->page->getTitle()->getPrefixedDBkey(), 'trigger' => 'view']); } if ($this->cacheable && $this->parserOutput->isCacheable() && $isCurrent) { ParserCache::singleton()->save($this->parserOutput, $this->page, $this->parserOptions, $cacheTime, $this->revid); } // Make sure file cache is not used on uncacheable content. // Output that has magic words in it can still use the parser cache // (if enabled), though it will generally expire sooner. if (!$this->parserOutput->isCacheable()) { $wgUseFileCache = false; } if ($isCurrent) { $this->page->triggerOpportunisticLinksUpdate($this->parserOutput); } return true; }
/** * Generate the navigation links when browsing through an article revisions * It shows the information as: * Revision as of \<date\>; view current revision * \<- Previous version | Next Version -\> * * @param $oldid String: revision ID of this article revision */ public function setOldSubtitle($oldid = 0) { global $wgLang, $wgOut, $wgUser, $wgRequest; if (!wfRunHooks('DisplayOldSubtitle', array(&$this, &$oldid))) { return; } $unhide = $wgRequest->getInt('unhide') == 1; # Cascade unhide param in links for easy deletion browsing $extraParams = array(); if ($wgRequest->getVal('unhide')) { $extraParams['unhide'] = 1; } $revision = Revision::newFromId($oldid); $timestamp = $revision->getTimestamp(); $current = $oldid == $this->mPage->getLatest(); $td = $wgLang->timeanddate($timestamp, true); $tddate = $wgLang->date($timestamp, true); $tdtime = $wgLang->time($timestamp, true); $lnk = $current ? wfMsgHtml('currentrevisionlink') : Linker::link($this->getTitle(), wfMsgHtml('currentrevisionlink'), array(), $extraParams, array('known', 'noclasses')); $curdiff = $current ? wfMsgHtml('diff') : Linker::link($this->getTitle(), wfMsgHtml('diff'), array(), array('diff' => 'cur', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')); $prev = $this->getTitle()->getPreviousRevisionID($oldid); $prevlink = $prev ? Linker::link($this->getTitle(), wfMsgHtml('previousrevision'), array(), array('direction' => 'prev', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')) : wfMsgHtml('previousrevision'); $prevdiff = $prev ? Linker::link($this->getTitle(), wfMsgHtml('diff'), array(), array('diff' => 'prev', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')) : wfMsgHtml('diff'); $nextlink = $current ? wfMsgHtml('nextrevision') : Linker::link($this->getTitle(), wfMsgHtml('nextrevision'), array(), array('direction' => 'next', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')); $nextdiff = $current ? wfMsgHtml('diff') : Linker::link($this->getTitle(), wfMsgHtml('diff'), array(), array('diff' => 'next', 'oldid' => $oldid) + $extraParams, array('known', 'noclasses')); $cdel = ''; // User can delete revisions or view deleted revisions... $canHide = $wgUser->isAllowed('deleterevision'); if ($canHide || $revision->getVisibility() && $wgUser->isAllowed('deletedhistory')) { if (!$revision->userCan(Revision::DELETED_RESTRICTED)) { $cdel = Linker::revDeleteLinkDisabled($canHide); // rev was hidden from Sysops } else { $query = array('type' => 'revision', 'target' => $this->getTitle()->getPrefixedDbkey(), 'ids' => $oldid); $cdel = Linker::revDeleteLink($query, $revision->isDeleted(File::DELETED_RESTRICTED), $canHide); } $cdel .= ' '; } # Show user links if allowed to see them. If hidden, then show them only if requested... $userlinks = Linker::revUserTools($revision, !$unhide); $infomsg = $current && !wfMessage('revision-info-current')->isDisabled() ? 'revision-info-current' : 'revision-info'; $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsgExt($infomsg, array('parseinline', 'replaceafter'), $td, $userlinks, $revision->getID(), $tddate, $tdtime, $revision->getUser()) . "</div>\n" . "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt('revision-nav', array('escapenoentities', 'parsemag', 'replaceafter'), $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff) . "</div>\n\t\t\t"; $wgOut->setSubtitle($r); }
/** * Generate the navigation links when browsing through an article revisions * It shows the information as: * Revision as of \<date\>; view current revision * \<- Previous version | Next Version -\> * * @param $oldid int: revision ID of this article revision */ public function setOldSubtitle($oldid = 0) { if (!wfRunHooks('DisplayOldSubtitle', array(&$this, &$oldid))) { return; } $unhide = $this->getContext()->getRequest()->getInt('unhide') == 1; # Cascade unhide param in links for easy deletion browsing $extraParams = array(); if ($unhide) { $extraParams['unhide'] = 1; } if ($this->mRevision && $this->mRevision->getId() === $oldid) { $revision = $this->mRevision; } else { $revision = Revision::newFromId($oldid); } $timestamp = $revision->getTimestamp(); $current = $oldid == $this->mPage->getLatest(); $language = $this->getContext()->getLanguage(); $user = $this->getContext()->getUser(); $td = $language->userTimeAndDate($timestamp, $user); $tddate = $language->userDate($timestamp, $user); $tdtime = $language->userTime($timestamp, $user); # Show user links if allowed to see them. If hidden, then show them only if requested... $userlinks = Linker::revUserTools($revision, !$unhide); $infomsg = $current && !wfMessage('revision-info-current')->isDisabled() ? 'revision-info-current' : 'revision-info'; $outputPage = $this->getContext()->getOutput(); $outputPage->addSubtitle("<div id=\"mw-{$infomsg}\">" . wfMessage($infomsg, $td)->rawParams($userlinks)->params($revision->getID(), $tddate, $tdtime, $revision->getUser())->parse() . "</div>"); $lnk = $current ? wfMessage('currentrevisionlink')->escaped() : Linker::linkKnown($this->getTitle(), wfMessage('currentrevisionlink')->escaped(), array(), $extraParams); $curdiff = $current ? wfMessage('diff')->escaped() : Linker::linkKnown($this->getTitle(), wfMessage('diff')->escaped(), array(), array('diff' => 'cur', 'oldid' => $oldid) + $extraParams); $prev = $this->getTitle()->getPreviousRevisionID($oldid); $prevlink = $prev ? Linker::linkKnown($this->getTitle(), wfMessage('previousrevision')->escaped(), array(), array('direction' => 'prev', 'oldid' => $oldid) + $extraParams) : wfMessage('previousrevision')->escaped(); $prevdiff = $prev ? Linker::linkKnown($this->getTitle(), wfMessage('diff')->escaped(), array(), array('diff' => 'prev', 'oldid' => $oldid) + $extraParams) : wfMessage('diff')->escaped(); $nextlink = $current ? wfMessage('nextrevision')->escaped() : Linker::linkKnown($this->getTitle(), wfMessage('nextrevision')->escaped(), array(), array('direction' => 'next', 'oldid' => $oldid) + $extraParams); $nextdiff = $current ? wfMessage('diff')->escaped() : Linker::linkKnown($this->getTitle(), wfMessage('diff')->escaped(), array(), array('diff' => 'next', 'oldid' => $oldid) + $extraParams); $cdel = Linker::getRevDeleteLink($user, $revision, $this->getTitle()); if ($cdel !== '') { $cdel .= ' '; } $outputPage->addSubtitle("<div id=\"mw-revision-nav\">" . $cdel . wfMessage('revision-nav')->rawParams($prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff)->escaped() . "</div>"); }
/** * @param $page WikiPage * @param $popts ParserOptions * @param $pageId Int * @param $getWikitext Bool * @return ParserOutput */ private function getParsedContent(WikiPage $page, $popts, $pageId = null, $getWikitext = false) { $this->content = $page->getContent(Revision::RAW); //XXX: really raw? if ($this->section !== false && $this->content !== null) { $this->content = $this->getSectionContent($this->content, !is_null($pageId) ? 'page id ' . $pageId : $page->getTitle()->getText()); // Not cached (save or load) return $this->content->getParserOutput($page->getTitle(), null, $popts); } else { // Try the parser cache first // getParserOutput will save to Parser cache if able $pout = $page->getParserOutput($popts); if (!$pout) { $this->dieUsage("There is no revision ID {$page->getLatest()}", 'missingrev'); } if ($getWikitext) { $this->content = $page->getContent(Revision::RAW); } return $pout; } }
/** * @param WikiPage $page * @param string|int $timestamp */ protected function forceRevisionDate(WikiPage $page, $timestamp) { $dbw = wfGetDB(DB_MASTER); $dbw->update('revision', array('rev_timestamp' => $dbw->timestamp($timestamp)), array('rev_id' => $page->getLatest())); $page->clear(); }
/** * Sets post-edit cookie indicating the user just saved a particular revision. * * This uses a temporary cookie for each revision ID so separate saves will never * interfere with each other. * * The cookie is deleted in the mediawiki.action.view.postEdit JS module after * the redirect. It must be clearable by JavaScript code, so it must not be * marked HttpOnly. The JavaScript code converts the cookie to a wgPostEdit config * variable. * * If the variable were set on the server, it would be cached, which is unwanted * since the post-edit state should only apply to the load right after the save. * * @param int $statusValue The status value (to check for new article status) */ protected function setPostEditCookie($statusValue) { $revisionId = $this->page->getLatest(); $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId; $val = 'saved'; if ($statusValue == self::AS_SUCCESS_NEW_ARTICLE) { $val = 'created'; } elseif ($this->oldid) { $val = 'restored'; } $response = RequestContext::getMain()->getRequest()->response(); $response->setCookie($postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, array('httpOnly' => false)); }
/** * @return bool */ protected function showHeader() { global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang; global $wgAllowUserCss, $wgAllowUserJs; $this->addTalkPageText(); $this->addEditNotices(); if ($this->isConflict) { $wgOut->wrapWikiMsg("<div class='mw-explainconflict'>\n\$1\n</div>", 'explainconflict'); $this->editRevId = $this->page->getLatest(); } else { if ($this->section != '' && !$this->isSectionEditSupported()) { // We use $this->section to much before this and getVal('wgSection') directly in other places // at this point we can't reset $this->section to '' to fallback to non-section editing. // Someone is welcome to try refactoring though $wgOut->showErrorPage('sectioneditnotsupported-title', 'sectioneditnotsupported-text'); return false; } if ($this->section != '' && $this->section != 'new') { if (!$this->summary && !$this->preview && !$this->diff) { $sectionTitle = self::extractSectionTitle($this->textbox1); // FIXME: use Content object if ($sectionTitle !== false) { $this->summary = "/* {$sectionTitle} */ "; } } } if ($this->missingComment) { $wgOut->wrapWikiMsg("<div id='mw-missingcommenttext'>\n\$1\n</div>", 'missingcommenttext'); } if ($this->missingSummary && $this->section != 'new') { $wgOut->wrapWikiMsg("<div id='mw-missingsummary'>\n\$1\n</div>", 'missingsummary'); } if ($this->missingSummary && $this->section == 'new') { $wgOut->wrapWikiMsg("<div id='mw-missingcommentheader'>\n\$1\n</div>", 'missingcommentheader'); } if ($this->blankArticle) { $wgOut->wrapWikiMsg("<div id='mw-blankarticle'>\n\$1\n</div>", 'blankarticle'); } if ($this->selfRedirect) { $wgOut->wrapWikiMsg("<div id='mw-selfredirect'>\n\$1\n</div>", 'selfredirect'); } if ($this->hookError !== '') { $wgOut->addWikiText($this->hookError); } if (!$this->checkUnicodeCompliantBrowser()) { $wgOut->addWikiMsg('nonunicodebrowser'); } if ($this->section != 'new') { $revision = $this->mArticle->getRevisionFetched(); if ($revision) { // Let sysop know that this will make private content public if saved if (!$revision->userCan(Revision::DELETED_TEXT, $wgUser)) { $wgOut->wrapWikiMsg("<div class='mw-warning plainlinks'>\n\$1\n</div>\n", 'rev-deleted-text-permission'); } elseif ($revision->isDeleted(Revision::DELETED_TEXT)) { $wgOut->wrapWikiMsg("<div class='mw-warning plainlinks'>\n\$1\n</div>\n", 'rev-deleted-text-view'); } if (!$revision->isCurrent()) { $this->mArticle->setOldSubtitle($revision->getId()); $wgOut->addWikiMsg('editingold'); $this->isOldRev = true; } } elseif ($this->mTitle->exists()) { // Something went wrong $wgOut->wrapWikiMsg("<div class='errorbox'>\n\$1\n</div>\n", ['missing-revision', $this->oldid]); } } } if (wfReadOnly()) { $wgOut->wrapWikiMsg("<div id=\"mw-read-only-warning\">\n\$1\n</div>", ['readonlywarning', wfReadOnlyReason()]); } elseif ($wgUser->isAnon()) { if ($this->formtype != 'preview') { $wgOut->wrapWikiMsg("<div id='mw-anon-edit-warning' class='warningbox'>\n\$1\n</div>", ['anoneditwarning', SpecialPage::getTitleFor('Userlogin')->getFullURL(['returnto' => $this->getTitle()->getPrefixedDBkey()]), SpecialPage::getTitleFor('CreateAccount')->getFullURL(['returnto' => $this->getTitle()->getPrefixedDBkey()])]); } else { $wgOut->wrapWikiMsg("<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n\$1</div>", 'anonpreviewwarning'); } } else { if ($this->isCssJsSubpage) { # Check the skin exists if ($this->isWrongCaseCssJsPage) { $wgOut->wrapWikiMsg("<div class='error' id='mw-userinvalidcssjstitle'>\n\$1\n</div>", ['userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage()]); } if ($this->getTitle()->isSubpageOf($wgUser->getUserPage())) { $wgOut->wrapWikiMsg('<div class="mw-usercssjspublic">$1</div>', $this->isCssSubpage ? 'usercssispublic' : 'userjsispublic'); if ($this->formtype !== 'preview') { if ($this->isCssSubpage && $wgAllowUserCss) { $wgOut->wrapWikiMsg("<div id='mw-usercssyoucanpreview'>\n\$1\n</div>", ['usercssyoucanpreview']); } if ($this->isJsSubpage && $wgAllowUserJs) { $wgOut->wrapWikiMsg("<div id='mw-userjsyoucanpreview'>\n\$1\n</div>", ['userjsyoucanpreview']); } } } } } if ($this->mTitle->isProtected('edit') && MWNamespace::getRestrictionLevels($this->mTitle->getNamespace()) !== ['']) { # Is the title semi-protected? if ($this->mTitle->isSemiProtected()) { $noticeMsg = 'semiprotectedpagewarning'; } else { # Then it must be protected based on static groups (regular) $noticeMsg = 'protectedpagewarning'; } LogEventsList::showLogExtract($wgOut, 'protect', $this->mTitle, '', ['lim' => 1, 'msgKey' => [$noticeMsg]]); } if ($this->mTitle->isCascadeProtected()) { # Is this page under cascading protection from some source pages? /** @var Title[] $cascadeSources */ list($cascadeSources, ) = $this->mTitle->getCascadeProtectionSources(); $notice = "<div class='mw-cascadeprotectedwarning'>\n\$1\n"; $cascadeSourcesCount = count($cascadeSources); if ($cascadeSourcesCount > 0) { # Explain, and list the titles responsible foreach ($cascadeSources as $page) { $notice .= '* [[:' . $page->getPrefixedText() . "]]\n"; } } $notice .= '</div>'; $wgOut->wrapWikiMsg($notice, ['cascadeprotectedwarning', $cascadeSourcesCount]); } if (!$this->mTitle->exists() && $this->mTitle->getRestrictions('create')) { LogEventsList::showLogExtract($wgOut, 'protect', $this->mTitle, '', ['lim' => 1, 'showIfEmpty' => false, 'msgKey' => ['titleprotectedwarning'], 'wrap' => "<div class=\"mw-titleprotectedwarning\">\n\$1</div>"]); } if ($this->contentLength === false) { $this->contentLength = strlen($this->textbox1); } if ($this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024) { $wgOut->wrapWikiMsg("<div class='error' id='mw-edit-longpageerror'>\n\$1\n</div>", ['longpageerror', $wgLang->formatNum(round($this->contentLength / 1024, 3)), $wgLang->formatNum($wgMaxArticleSize)]); } else { if (!$this->context->msg('longpage-hint')->isDisabled()) { $wgOut->wrapWikiMsg("<div id='mw-edit-longpage-hint'>\n\$1\n</div>", ['longpage-hint', $wgLang->formatSize(strlen($this->textbox1)), strlen($this->textbox1)]); } } # Add header copyright warning $this->showHeaderCopyrightWarning(); return true; }
function approveEditById($id) { $dbw = wfGetDB(DB_MASTER); $row = $dbw->selectRow('moderation', array('mod_id AS id', 'mod_timestamp AS timestamp', 'mod_user AS user', 'mod_user_text AS user_text', 'mod_cur_id AS cur_id', 'mod_namespace AS namespace', 'mod_title AS title', 'mod_comment AS comment', 'mod_minor AS minor', 'mod_bot AS bot', 'mod_last_oldid AS last_oldid', 'mod_ip AS ip', 'mod_header_xff AS header_xff', 'mod_header_ua AS header_ua', 'mod_text AS text', 'mod_merged_revid AS merged_revid', 'mod_rejected AS rejected', 'mod_stash_key AS stash_key'), array('mod_id' => $id), __METHOD__); if (!$row) { throw new ModerationError('moderation-edit-not-found'); } if ($row->merged_revid) { throw new ModerationError('moderation-already-merged'); } if ($row->rejected && $row->timestamp < $this->mSpecial->earliestReapprovableTimestamp) { throw new ModerationError('moderation-rejected-long-ago'); } # Prepare everything $title = Title::makeTitle($row->namespace, $row->title); $model = $title->getContentModel(); $user = $row->user ? User::newFromId($row->user) : User::newFromName($row->user_text, false); $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY; if ($row->bot && $user->isAllowed('bot')) { $flags |= EDIT_FORCE_BOT; } if ($row->minor) { # doEditContent() checks the right $flags |= EDIT_MINOR; } # For CheckUser extension to work properly, IP, XFF and UA # should be set to the correct values for the original user # (not from the moderator) $cuHook = new ModerationCheckUserHook(); $cuHook->install($row->ip, $row->header_xff, $row->header_ua); $approveHook = new ModerationApproveHook(); $approveHook->install(array('rev_timestamp' => $dbw->timestamp($row->timestamp), 'rev_user' => $user->getId(), 'rev_user_text' => $user->getName())); $status = Status::newGood(); if ($row->stash_key) { # This is the upload from stash. $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash($user); try { $file = $stash->getFile($row->stash_key); } catch (MWException $e) { throw new ModerationError('moderation-missing-stashed-image'); } $upload = new UploadFromStash($user, $stash); $upload->initialize($row->stash_key, $title->getText()); $status = $upload->performUpload($row->comment, $row->text, 0, $user); } else { # This is normal edit (not an upload). $new_content = ContentHandler::makeContent($row->text, null, $model); $page = new WikiPage($title); if (!$page->exists()) { # New page $status = $page->doEditContent($new_content, $row->comment, $flags, false, $user); } else { # Existing page $latest = $page->getLatest(); if ($latest == $row->last_oldid) { # Page hasn't changed since this edit was queued for moderation. $status = $page->doEditContent($new_content, $row->comment, $flags, $row->last_oldid, $user); } else { # Page has changed! # Let's attempt merging, as MediaWiki does in private EditPage::mergeChangesIntoContent(). $base_content = $row->last_oldid ? Revision::newFromId($row->last_oldid)->getContent(Revision::RAW) : ContentHandler::makeContent('', null, $model); $latest_content = Revision::newFromId($latest)->getContent(Revision::RAW); $handler = ContentHandler::getForModelID($base_content->getModel()); $merged_content = $handler->merge3($base_content, $new_content, $latest_content); if ($merged_content) { $status = $page->doEditContent($merged_content, $row->comment, $flags, $latest, $user); } else { $dbw = wfGetDB(DB_MASTER); $dbw->update('moderation', array('mod_conflict' => 1), array('mod_id' => $id), __METHOD__); $dbw->commit(__METHOD__); throw new ModerationError('moderation-edit-conflict'); } } } } $approveHook->deinstall(); $cuHook->deinstall(); if (!$status->isGood()) { throw new ModerationError($status->getMessage()); } $logEntry = new ManualLogEntry('moderation', 'approve'); $logEntry->setPerformer($this->moderator); $logEntry->setTarget($title); $logEntry->setParameters(array('revid' => $approveHook->lastRevId)); $logid = $logEntry->insert(); $logEntry->publish($logid); # Approved edits are removed from "moderation" table, # because they already exist in page history, recentchanges etc. $dbw = wfGetDB(DB_MASTER); $dbw->delete('moderation', array('mod_id' => $id), __METHOD__); }