public static function provideHandlers() { $models = ContentHandler::getContentModels(); $handlers = []; foreach ($models as $model) { $handlers[] = [ContentHandler::getForModelID($model)]; } return $handlers; }
/** * Show an edit conflict. textbox1 is already shown in showEditForm(). * If you want to use another entry point to this function, be careful. */ protected function showConflict() { global $wgOut; if (wfRunHooks('EditPageBeforeConflictDiff', array(&$this, &$wgOut))) { $wgOut->wrapWikiMsg('<h2>$1</h2>', "yourdiff"); $content1 = $this->toEditContent($this->textbox1); $content2 = $this->toEditContent($this->textbox2); $handler = ContentHandler::getForModelID($this->contentModel); $de = $handler->createDifferenceEngine($this->mArticle->getContext()); $de->setContent($content2, $content1); $de->showDiff(wfMessage('yourtext')->parse(), wfMessage('storedversion')->text()); $wgOut->wrapWikiMsg('<h2>$1</h2>', "yourtext"); $this->showTextbox2(); } }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); if ($user->isBot()) { // sanity $this->dieUsage('This interface is not supported for bots', 'botsnotsupported'); } $cache = ObjectCache::getLocalClusterInstance(); $page = $this->getTitleOrPageId($params); $title = $page->getTitle(); if (!ContentHandler::getForModelID($params['contentmodel'])->isSupportedFormat($params['contentformat'])) { $this->dieUsage('Unsupported content model/format', 'badmodelformat'); } $text = null; $textHash = null; if (strlen($params['stashedtexthash'])) { // Load from cache since the client indicates the text is the same as last stash $textHash = $params['stashedtexthash']; $textKey = $cache->makeKey('stashedit', 'text', $textHash); $text = $cache->get($textKey); if (!is_string($text)) { $this->dieUsage('No stashed text found with the given hash', 'missingtext'); } } elseif ($params['text'] !== null) { // Trim and fix newlines so the key SHA1's match (see WebRequest::getText()) $text = rtrim(str_replace("\r\n", "\n", $params['text'])); $textHash = sha1($text); } else { $this->dieUsage('The text or stashedtexthash parameter must be given', 'missingtextparam'); } $textContent = ContentHandler::makeContent($text, $title, $params['contentmodel'], $params['contentformat']); $page = WikiPage::factory($title); if ($page->exists()) { // Page exists: get the merged content with the proposed change $baseRev = Revision::newFromPageId($page->getId(), $params['baserevid']); if (!$baseRev) { $this->dieUsage("No revision ID {$params['baserevid']}", 'missingrev'); } $currentRev = $page->getRevision(); if (!$currentRev) { $this->dieUsage("No current revision of page ID {$page->getId()}", 'missingrev'); } // Merge in the new version of the section to get the proposed version $editContent = $page->replaceSectionAtRev($params['section'], $textContent, $params['sectiontitle'], $baseRev->getId()); if (!$editContent) { $this->dieUsage('Could not merge updated section.', 'replacefailed'); } if ($currentRev->getId() == $baseRev->getId()) { // Base revision was still the latest; nothing to merge $content = $editContent; } else { // Merge the edit into the current version $baseContent = $baseRev->getContent(); $currentContent = $currentRev->getContent(); if (!$baseContent || !$currentContent) { $this->dieUsage("Missing content for page ID {$page->getId()}", 'missingrev'); } $handler = ContentHandler::getForModelID($baseContent->getModel()); $content = $handler->merge3($baseContent, $editContent, $currentContent); } } else { // New pages: use the user-provided content model $content = $textContent; } if (!$content) { // merge3() failed $this->getResult()->addValue(null, $this->getModuleName(), ['status' => 'editconflict']); return; } // The user will abort the AJAX request by pressing "save", so ignore that ignore_user_abort(true); if ($user->pingLimiter('stashedit')) { $status = 'ratelimited'; } else { $status = self::parseAndStash($page, $content, $user, $params['summary']); $textKey = $cache->makeKey('stashedit', 'text', $textHash); $cache->set($textKey, $text, self::MAX_CACHE_TTL); } $stats = MediaWikiServices::getInstance()->getStatsdDataFactory(); $stats->increment("editstash.cache_stores.{$status}"); $this->getResult()->addValue(null, $this->getModuleName(), ['status' => $status, 'texthash' => $textHash]); }
/** * @return ContentHandler */ function getContentHandler() { if (is_null($this->contentHandler)) { $this->contentHandler = ContentHandler::getForModelID($this->getModel()); } return $this->contentHandler; }
public function execute() { $out = $this->mSpecial->getOutput(); $out->addModuleStyles('mediawiki.action.history.diff'); $dbr = wfGetDB(DB_SLAVE); $row = $dbr->selectRow('moderation', array('mod_user AS user', 'mod_user_text AS user_text', 'mod_last_oldid AS last_oldid', 'mod_cur_id AS cur_id', 'mod_namespace AS namespace', 'mod_title AS title', 'mod_text AS text', 'mod_stash_key AS stash_key'), array('mod_id' => $this->id), __METHOD__); if (!$row) { throw new ModerationError('moderation-edit-not-found'); } $title = Title::makeTitle($row->namespace, $row->title); $model = $title->getContentModel(); $out->setPageTitle(wfMessage('difference-title', $title->getPrefixedText())); $old_content = false; if ($row->cur_id != 0) { # Existing page $rev = Revision::newFromId($row->last_oldid); if ($rev) { $old_content = $rev->getContent(Revision::RAW); $model = $old_content->getModel(); } } if (!$old_content) { # New or previously deleted page $old_content = ContentHandler::makeContent("", null, $model); } if ($row->stash_key) { $url_params = array('modaction' => 'showimg', 'modid' => $this->id); $url_full = $this->mSpecial->getTitle()->getLinkURL($url_params); # Check if this file is not an image (e.g. OGG file) $is_image = 1; $user = $row->user ? User::newFromId($row->user) : User::newFromName($row->user_text, false); $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash($user); try { $meta = $stash->getMetadata($row->stash_key); if ($meta['us_media_type'] != 'BITMAP' && $meta['us_media_type'] != 'DRAWING') { $is_image = 0; } } catch (MWException $e) { # If we can't find it, thumbnail won't work either $is_image = 0; } if ($is_image) { $url_params['thumb'] = 1; $url_thumb = $this->mSpecial->getTitle()->getLinkURL($url_params); $html_img = Xml::element('img', array('src' => $url_thumb)); } else { # Not an image, so no thumbnail is needed. # Just print a filename. $html_img = $title->getFullText(); } $html_a = Xml::tags('a', array('href' => $url_full), $html_img); $out->addHTML($html_a); } $de = ContentHandler::getForModelID($model)->createDifferenceEngine($this->mSpecial->getContext(), $row->last_oldid, 0, 0, 0, 0); $diff = ''; if (!$row->stash_key || !$title->exists()) { $new_content = ContentHandler::makeContent($row->text, null, $model); $diff = $de->generateContentDiffBody($old_content, $new_content); } if ($diff) { // TODO: add more information into headers (username, timestamp etc.), as in usual diffs $header_before = wfMessage('moderation-diff-header-before')->text(); $header_after = wfMessage('moderation-diff-header-after')->text(); $out->addHTML($de->addHeader($diff, $header_before, $header_after)); } else { $out->addWikiMsg($row->stash_key ? $title->exists() ? 'moderation-diff-reupload' : 'moderation-diff-upload-notext' : 'moderation-diff-no-changes'); } }
/** * Extract information from the Revision * * @param Revision $revision * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set * @return array */ protected function extractRevisionInfo(Revision $revision, $row) { $title = $revision->getTitle(); $user = $this->getUser(); $vals = array(); $anyHidden = false; if ($this->fld_ids) { $vals['revid'] = intval($revision->getId()); if (!is_null($revision->getParentId())) { $vals['parentid'] = intval($revision->getParentId()); } } if ($this->fld_flags) { $vals['minor'] = $revision->isMinor(); } if ($this->fld_user || $this->fld_userid) { if ($revision->isDeleted(Revision::DELETED_USER)) { $vals['userhidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $revision->getUserText(Revision::RAW); } $userid = $revision->getUser(Revision::RAW); if (!$userid) { $vals['anon'] = true; } if ($this->fld_userid) { $vals['userid'] = $userid; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp()); } if ($this->fld_size) { if (!is_null($revision->getSize())) { $vals['size'] = intval($revision->getSize()); } else { $vals['size'] = 0; } } if ($this->fld_sha1) { if ($revision->isDeleted(Revision::DELETED_TEXT)) { $vals['sha1hidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_TEXT, $user)) { if ($revision->getSha1() != '') { $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40); } else { $vals['sha1'] = ''; } } } if ($this->fld_contentmodel) { $vals['contentmodel'] = $revision->getContentModel(); } if ($this->fld_comment || $this->fld_parsedcomment) { if ($revision->isDeleted(Revision::DELETED_COMMENT)) { $vals['commenthidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_COMMENT, $user)) { $comment = $revision->getComment(Revision::RAW); if ($this->fld_comment) { $vals['comment'] = $comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } $content = null; global $wgParser; if ($this->fetchContent) { $content = $revision->getContent(Revision::FOR_THIS_USER, $this->getUser()); // Expand templates after getting section content because // template-added sections don't count and Parser::preprocess() // will have less input if ($content && $this->section !== false) { $content = $content->getSection($this->section, false); if (!$content) { $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection'); } } if ($revision->isDeleted(Revision::DELETED_TEXT)) { $vals['texthidden'] = true; $anyHidden = true; } elseif (!$content) { $vals['textmissing'] = true; } } if ($this->fld_content && $content) { $text = null; if ($this->generateXML) { if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $t = $content->getNativeData(); # note: don't set $text $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), Parser::OT_PREPROCESS); $dom = $wgParser->preprocessToDom($t); if (is_callable(array($dom, 'saveXML'))) { $xml = $dom->saveXML(); } else { $xml = $dom->__toString(); } $vals['parsetree'] = $xml; } else { $vals['badcontentformatforparsetree'] = true; $this->setWarning("Conversion to XML is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel()); } } if ($this->expandTemplates && !$this->parseContent) { #XXX: implement template expansion for all content types in ContentHandler? if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $text = $content->getNativeData(); $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext())); } else { $this->setWarning("Template expansion is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel()); $vals['badcontentformat'] = true; $text = false; } } if ($this->parseContent) { $po = $content->getParserOutput($title, $revision->getId(), ParserOptions::newFromContext($this->getContext())); $text = $po->getText(); } if ($text === null) { $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat(); $model = $content->getModel(); if (!$content->isSupportedFormat($format)) { $name = $title->getPrefixedDBkey(); $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}"); $vals['badcontentformat'] = true; $text = false; } else { $text = $content->serialize($format); // always include format and model. // Format is needed to deserialize, model is needed to interpret. $vals['contentformat'] = $format; $vals['contentmodel'] = $model; } } if ($text !== false) { ApiResult::setContentValue($vals, 'content', $text); } } if ($content && (!is_null($this->diffto) || !is_null($this->difftotext))) { static $n = 0; // Number of uncached diffs we've had if ($n < $this->getConfig()->get('APIMaxUncachedDiffs')) { $vals['diff'] = array(); $context = new DerivativeContext($this->getContext()); $context->setTitle($title); $handler = $revision->getContentHandler(); if (!is_null($this->difftotext)) { $model = $title->getContentModel(); if ($this->contentFormat && !ContentHandler::getForModelID($model)->isSupportedFormat($this->contentFormat)) { $name = $title->getPrefixedDBkey(); $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}"); $vals['diff']['badcontentformat'] = true; $engine = null; } else { $difftocontent = ContentHandler::makeContent($this->difftotext, $title, $model, $this->contentFormat); $engine = $handler->createDifferenceEngine($context); $engine->setContent($content, $difftocontent); } } else { $engine = $handler->createDifferenceEngine($context, $revision->getID(), $this->diffto); $vals['diff']['from'] = $engine->getOldid(); $vals['diff']['to'] = $engine->getNewid(); } if ($engine) { $difftext = $engine->getDiffBody(); ApiResult::setContentValue($vals['diff'], 'body', $difftext); if (!$engine->wasCacheHit()) { $n++; } } } else { $vals['diff']['notcached'] = true; } } if ($anyHidden && $revision->isDeleted(Revision::DELETED_RESTRICTED)) { $vals['suppressed'] = true; } return $vals; }
public static function getAllContentFormats() { global $wgContentHandlers; $formats = array(); foreach ($wgContentHandlers as $model => $class) { $handler = ContentHandler::getForModelID($model); $formats = array_merge($formats, $handler->getSupportedFormats()); } $formats = array_unique($formats); return $formats; }
public function execute() { global $wgMemc; $user = $this->getUser(); $params = $this->extractRequestParams(); $page = $this->getTitleOrPageId($params); $title = $page->getTitle(); if (!ContentHandler::getForModelID($params['contentmodel'])->isSupportedFormat($params['contentformat'])) { $this->dieUsage("Unsupported content model/format", 'badmodelformat'); } // Trim and fix newlines so the key SHA1's match (see RequestContext::getText()) $text = rtrim(str_replace("\r\n", "\n", $params['text'])); $textContent = ContentHandler::makeContent($text, $title, $params['contentmodel'], $params['contentformat']); $page = WikiPage::factory($title); if ($page->exists()) { // Page exists: get the merged content with the proposed change $baseRev = Revision::newFromPageId($page->getId(), $params['baserevid']); if (!$baseRev) { $this->dieUsage("No revision ID {$params['baserevid']}", 'missingrev'); } $currentRev = $page->getRevision(); if (!$currentRev) { $this->dieUsage("No current revision of page ID {$page->getId()}", 'missingrev'); } // Merge in the new version of the section to get the proposed version $editContent = $page->replaceSectionAtRev($params['section'], $textContent, $params['sectiontitle'], $baseRev->getId()); if (!$editContent) { $this->dieUsage("Could not merge updated section.", 'replacefailed'); } if ($currentRev->getId() == $baseRev->getId()) { // Base revision was still the latest; nothing to merge $content = $editContent; } else { // Merge the edit into the current version $baseContent = $baseRev->getContent(); $currentContent = $currentRev->getContent(); if (!$baseContent || !$currentContent) { $this->dieUsage("Missing content for page ID {$page->getId()}", 'missingrev'); } $handler = ContentHandler::getForModelID($baseContent->getModel()); $content = $handler->merge3($baseContent, $editContent, $currentContent); } } else { // New pages: use the user-provided content model $content = $textContent; } if (!$content) { // merge3() failed $this->getResult()->addValue(null, $this->getModuleName(), array('status' => 'editconflict')); return; } // The user will abort the AJAX request by pressing "save", so ignore that ignore_user_abort(true); // Get a key based on the source text, format, and user preferences $key = self::getStashKey($title, $content, $user); // De-duplicate requests on the same key if ($user->pingLimiter('stashedit')) { $status = 'ratelimited'; } elseif ($wgMemc->lock($key, 0, 30)) { /** @noinspection PhpUnusedLocalVariableInspection */ $unlocker = new ScopedCallback(function () use($key) { global $wgMemc; $wgMemc->unlock($key); }); $status = self::parseAndStash($page, $content, $user); } else { $status = 'busy'; } $this->getResult()->addValue(null, $this->getModuleName(), array('status' => $status)); }
/** * @dataProvider provideGetModelForID */ public function testGetModelForID($modelId, $handlerClass) { $handler = ContentHandler::getForModelID($modelId); $this->assertInstanceOf($handlerClass, $handler); }
public static function onShowMissingArticle(Article $article) { if ($article->getPage()->getContentModel() !== CONTENT_MODEL_FLOW_BOARD) { return true; } if ($article->getTitle()->getNamespace() === NS_TOPIC) { // @todo pretty message about invalid workflow throw new FlowException('Non-existent topic'); } $emptyContent = ContentHandler::getForModelID(CONTENT_MODEL_FLOW_BOARD)->makeEmptyContent(); $parserOutput = $emptyContent->getParserOutput($article->getTitle()); $article->getContext()->getOutput()->addParserOutput($parserOutput); return false; }
public function testGetFieldsForSearchIndex() { $searchEngine = $this->newSearchEngine(); $handler = ContentHandler::getForModelID(CONTENT_MODEL_WIKITEXT); $fields = $handler->getFieldsForSearchIndex($searchEngine); $this->assertArrayHasKey('category', $fields); $this->assertArrayHasKey('external_link', $fields); $this->assertArrayHasKey('outgoing_link', $fields); $this->assertArrayHasKey('template', $fields); }
function writeOutput($par) { global $wgLang, $wgMemc, $wgDBname, $wgUser; global $wgSitename, $wgLanguageCode; global $wgFeedClasses, $wgFilterCallback, $wgWhitelistEdit, $wgParser; $this->getOutput()->setRobotpolicy("noindex,nofollow"); $target = !empty($par) ? $par : $this->getRequest()->getVal("target"); $t = Title::newFromDBKey($target); $update = true; if (!$t || !$t->userCan('edit')) { return; } if (!$this->getUser()->isAllowed('edit')) { return; } $article = new Article($t); $user = $this->getUser()->getName(); $real_name = User::whoIsReal($this->getUser()->getID()); if ($real_name == "") { $real_name = $user; } $dateStr = $wgLang->timeanddate(wfTimestampNow()); $comment = $this->getRequest()->getVal("comment_text"); foreach ($this->getRequest()->getValues() as $key => $value) { if (strpos($key, "comment_text") === 0) { $comment = $value; break; } } $topic = $this->getRequest()->getVal("topic_name"); //echo "$dateStr<br/>"; // remove leading space, tends to be a problem with a lot of talk page comments as it breaks the // HTML on the page $comment = preg_replace('/\\n[ ]*/', "\n", trim($comment)); // Check to see if the user is also getting a thumbs up. If so, append the thumbs message and give a thumbs up if ($this->getRequest()->getVal('thumb')) { $comment .= "\n\n" . wfMsg('qn_thumbs_up'); $userName = explode(":", $this->getRequest()->getVal('target')); ThumbsUp::quickNoteThumb($this->getRequest()->getVal('revold'), $this->getRequest()->getVal('revnew'), $this->getRequest()->getVal('pageid'), $userName[1]); } $formattedComment = wfMsg('postcomment_formatted_comment', $dateStr, $user, $real_name, $comment); if ($this->getRequest()->getVal('fromajax') == 'true') { $this->getOutput()->setArticleBodyOnly(true); } $text = ""; $r = Revision::newFromTitle($t); if ($r) { $text = $r->getText(); } $text .= "\n\n{$formattedComment}\n\n"; $this->getOutput()->setStatusCode(409); //echo "updating with text:<br/> $text"; //exit; $tmp = ""; if ($this->getUser()->isBlocked()) { $this->getOutput()->blockedPage(); return; } if (!$this->getUser()->getID() && $wgWhitelistEdit) { $this->userNotLoggedInPage(); return; } if (wfReadOnly()) { $this->getOutput()->readOnlyPage(); return; } if ($target == "Spam-Blacklist") { $this->getOutput()->readOnlyPage(); return; } if ($this->getUser()->pingLimiter()) { $this->getOutput()->rateLimited(); return; } $editPage = new EditPage($article); $contentModel = $t->getContentModel(); $handler = ContentHandler::getForModelID($contentModel); $contentFormat = $handler->getDefaultFormat(); $content = ContentHandler::makeContent($text, $t, $contentModel, $contentFormat); $status = Status::newGood(); if (!wfRunHooks('EditFilterMergedContent', array($this->getContext(), $content, &$status, '', $wgUser, false))) { return; } if (!$status->isGood()) { $errors = $status->getErrorsArray(true); foreach ($errors as $error) { if (is_array($error)) { $error = count($error) ? $error[0] : ''; } if (preg_match('@^spamprotection@', $error)) { $message = 'Error: found spam link'; $this->getOutput()->addHTML($message); return; } } $message = 'EditFilterMergedContent returned an error -- cannot post comment'; return; } $matches = array(); $preg = "/http:\\/\\/[^] \n'\">]*/"; $mod = str_ireplace('http://www.wikihow.com', '', $comment); preg_match_all($preg, $mod, $matches); if (sizeof($matches[0]) > 2) { $this->getOutput()->showErrorPage("postcomment", "postcomment_urls_limit"); return; } if (trim(strip_tags($comment)) == "") { $this->getOutput()->showErrorPage("postcomment", "postcomment_nopostingtoadd"); return; } if (!$t->userCan('edit')) { $this->getOutput()->showErrorPage("postcomment", "postcomment_discussionprotected"); return; } $watch = false; if ($this->getUser()->getID() > 0) { $watch = $this->getUser()->isWatched($t); } $fc = new FancyCaptcha(); $pass_captcha = $fc->passCaptcha(); if (!$pass_captcha && $this->getUser()->getID() == 0) { $this->getOutput()->addHTML("Sorry, please enter the correct word. Click <a onclick='window.location.reload(true);'>here</a> to get a new one.<br/><br/>"); return; } $article->doEdit($text, ""); if ($this->getRequest()->getVal('jsonresponse') == 'true') { $this->revId = $article->getRevIdFetched(); } // Notify users of usertalk updates if ($t->getNamespace() == NS_USER_TALK) { AuthorEmailNotification::notifyUserTalk($t->getArticleID(), $this->getUser()->getID(), $comment); } $this->getOutput()->setStatusCode(200); if ($this->getRequest()->getVal('fromajax') == 'true') { $this->getOutput()->redirect(''); $this->getContext()->setTitle($t); $formattedComment = $wgParser->preSaveTransform($formattedComment, $t, $this->getUser(), new ParserOptions()); $this->getOutput()->addHTML($this->getOutput()->parse("\n" . $formattedComment)); return; } }
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__); }
protected function setUp() { parent::setUp(); $this->handler = ContentHandler::getForModelID(CONTENT_MODEL_WIKITEXT); }
private function extractRowInfo($row) { $revision = new Revision($row); $title = $revision->getTitle(); $vals = array(); if ($this->fld_ids) { $vals['revid'] = intval($revision->getId()); // $vals['oldid'] = intval( $row->rev_text_id ); // todo: should this be exposed? if (!is_null($revision->getParentId())) { $vals['parentid'] = intval($revision->getParentId()); } } if ($this->fld_flags && $revision->isMinor()) { $vals['minor'] = ''; } if ($this->fld_user || $this->fld_userid) { if ($revision->isDeleted(Revision::DELETED_USER)) { $vals['userhidden'] = ''; } else { if ($this->fld_user) { $vals['user'] = $revision->getUserText(); } $userid = $revision->getUser(); if (!$userid) { $vals['anon'] = ''; } if ($this->fld_userid) { $vals['userid'] = $userid; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp()); } if ($this->fld_size) { if (!is_null($revision->getSize())) { $vals['size'] = intval($revision->getSize()); } else { $vals['size'] = 0; } } if ($this->fld_sha1) { if ($revision->getSha1() != '') { $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40); } else { $vals['sha1'] = ''; } } if ($this->fld_contentmodel) { $vals['contentmodel'] = $revision->getContentModel(); } if ($this->fld_comment || $this->fld_parsedcomment) { if ($revision->isDeleted(Revision::DELETED_COMMENT)) { $vals['commenthidden'] = ''; } else { $comment = $revision->getComment(); if ($this->fld_comment) { $vals['comment'] = $comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); $this->getResult()->setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } if (!is_null($this->token)) { $tokenFunctions = $this->getTokenFunctions(); foreach ($this->token as $t) { $val = call_user_func($tokenFunctions[$t], $title->getArticleID(), $title, $revision); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $vals[$t . 'token'] = $val; } } } $content = null; global $wgParser; if ($this->fld_content || !is_null($this->difftotext)) { $content = $revision->getContent(); // Expand templates after getting section content because // template-added sections don't count and Parser::preprocess() // will have less input if ($this->section !== false) { $content = $content->getSection($this->section, false); if (!$content) { $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection'); } } } if ($this->fld_content && !$revision->isDeleted(Revision::DELETED_TEXT)) { $text = null; if ($this->generateXML) { if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $t = $content->getNativeData(); # note: don't set $text $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), OT_PREPROCESS); $dom = $wgParser->preprocessToDom($t); if (is_callable(array($dom, 'saveXML'))) { $xml = $dom->saveXML(); } else { $xml = $dom->__toString(); } $vals['parsetree'] = $xml; } else { $this->setWarning("Conversion to XML is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel() . ")"); } } if ($this->expandTemplates && !$this->parseContent) { #XXX: implement template expansion for all content types in ContentHandler? if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $text = $content->getNativeData(); $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext())); } else { $this->setWarning("Template expansion is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel() . ")"); $text = false; } } if ($this->parseContent) { $po = $content->getParserOutput($title, $revision->getId(), ParserOptions::newFromContext($this->getContext())); $text = $po->getText(); } if ($text === null) { $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat(); if (!$content->isSupportedFormat($format)) { $model = $content->getModel(); $name = $title->getPrefixedDBkey(); $this->dieUsage("The requested format {$this->contentFormat} is not supported " . "for content model {$model} used by {$name}", 'badformat'); } $text = $content->serialize($format); $vals['contentformat'] = $format; } if ($text !== false) { ApiResult::setContent($vals, $text); } } elseif ($this->fld_content) { $vals['texthidden'] = ''; } if (!is_null($this->diffto) || !is_null($this->difftotext)) { global $wgAPIMaxUncachedDiffs; static $n = 0; // Number of uncached diffs we've had if ($n < $wgAPIMaxUncachedDiffs) { $vals['diff'] = array(); $context = new DerivativeContext($this->getContext()); $context->setTitle($title); $handler = $revision->getContentHandler(); if (!is_null($this->difftotext)) { $model = $title->getContentModel(); if ($this->contentFormat && !ContentHandler::getForModelID($model)->isSupportedFormat($this->contentFormat)) { $name = $title->getPrefixedDBkey(); $this->dieUsage("The requested format {$this->contentFormat} is not supported for " . "content model {$model} used by {$name}", 'badformat'); } $difftocontent = ContentHandler::makeContent($this->difftotext, $title, $model, $this->contentFormat); $engine = $handler->createDifferenceEngine($context); $engine->setContent($content, $difftocontent); } else { $engine = $handler->createDifferenceEngine($context, $revision->getID(), $this->diffto); $vals['diff']['from'] = $engine->getOldid(); $vals['diff']['to'] = $engine->getNewid(); } $difftext = $engine->getDiffBody(); ApiResult::setContent($vals['diff'], $difftext); if (!$engine->wasCacheHit()) { $n++; } } else { $vals['diff']['notcached'] = ''; } } return $vals; }
protected function checkContentModel() { global $wgContentHandlerUseDB; $title = $this->getTitle(); //note: may return null for revisions that have not yet been inserted. $model = $this->getContentModel(); $format = $this->getContentFormat(); $handler = $this->getContentHandler(); if (!$handler->isSupportedFormat($format)) { $t = $title->getPrefixedDBkey(); throw new MWException("Can't use format {$format} with content model {$model} on {$t}"); } if (!$wgContentHandlerUseDB && $title) { // if $wgContentHandlerUseDB is not set, all revisions must use the default content model and format. $defaultModel = ContentHandler::getDefaultModelFor($title); $defaultHandler = ContentHandler::getForModelID($defaultModel); $defaultFormat = $defaultHandler->getDefaultFormat(); if ($this->getContentModel() != $defaultModel) { $t = $title->getPrefixedDBkey(); throw new MWException("Can't save non-default content model with \$wgContentHandlerUseDB disabled: " . "model is {$model} , default for {$t} is {$defaultModel}"); } if ($this->getContentFormat() != $defaultFormat) { $t = $title->getPrefixedDBkey(); throw new MWException("Can't use non-default content format with \$wgContentHandlerUseDB disabled: " . "format is {$format}, default for {$t} is {$defaultFormat}"); } } $content = $this->getContent(Revision::RAW); if (!$content || !$content->isValid()) { $t = $title->getPrefixedDBkey(); throw new MWException("Content of {$t} is not valid! Content model is {$model}"); } }
/** * Applies applicable export transformations to $text. * * @param string $text * @param string $model * @param string|null $format * * @return string */ private function exportTransform($text, $model, $format = null) { try { $handler = ContentHandler::getForModelID($model); $text = $handler->exportTransform($text, $format); } catch (MWException $ex) { $this->progress("Unable to apply export transformation for content model '{$model}': " . $ex->getMessage()); } return $text; }
public function onSubmit(array $data) { global $wgContLang; if ($data['pagetitle'] === '') { // Initial form view of special page, pass return false; } // At this point, it has to be a POST request. This is enforced by HTMLForm, // but lets be safe verify that. if (!$this->getRequest()->wasPosted()) { throw new RuntimeException("Form submission was not POSTed"); } $this->title = Title::newFromText($data['pagetitle']); $user = $this->getUser(); // Check permissions and make sure the user has permission to edit the specific page $errors = $this->title->getUserPermissionsErrors('editcontentmodel', $user); $errors = wfMergeErrorArrays($errors, $this->title->getUserPermissionsErrors('edit', $user)); if ($errors) { $out = $this->getOutput(); $wikitext = $out->formatPermissionsErrorMessage($errors); // Hack to get our wikitext parsed return Status::newFatal(new RawMessage('$1', array($wikitext))); } $page = WikiPage::factory($this->title); if ($this->oldRevision === null) { $this->oldRevision = $page->getRevision() ?: false; } $oldModel = $this->title->getContentModel(); if ($this->oldRevision) { $oldContent = $this->oldRevision->getContent(); try { $newContent = ContentHandler::makeContent($oldContent->getNativeData(), $this->title, $data['model']); } catch (MWException $e) { return Status::newFatal($this->msg('changecontentmodel-cannot-convert')->params($this->title->getPrefixedText(), ContentHandler::getLocalizedName($data['model']))); } } else { // Page doesn't exist, create an empty content object $newContent = ContentHandler::getForModelID($data['model'])->makeEmptyContent(); } $flags = $this->oldRevision ? EDIT_UPDATE : EDIT_NEW; if ($user->isAllowed('bot')) { $flags |= EDIT_FORCE_BOT; } $log = new ManualLogEntry('contentmodel', 'change'); $log->setPerformer($user); $log->setTarget($this->title); $log->setComment($data['reason']); $log->setParameters(array('4::oldmodel' => $oldModel, '5::newmodel' => $data['model'])); $formatter = LogFormatter::newFromEntry($log); $formatter->setContext(RequestContext::newExtraneousContext($this->title)); $reason = $formatter->getPlainActionText(); if ($data['reason'] !== '') { $reason .= $this->msg('colon-separator')->inContentLanguage()->text() . $data['reason']; } # Truncate for whole multibyte characters. $reason = $wgContLang->truncate($reason, 255); $status = $page->doEditContent($newContent, $reason, $flags, $this->oldRevision ? $this->oldRevision->getId() : false, $user); if (!$status->isOK()) { return $status; } $logid = $log->insert(); $log->publish($logid); return $status; }
/** * Show an edit conflict. textbox1 is already shown in showEditForm(). * If you want to use another entry point to this function, be careful. */ protected function showConflict() { global $wgOut; if (Hooks::run('EditPageBeforeConflictDiff', [&$this, &$wgOut])) { $stats = $wgOut->getContext()->getStats(); $stats->increment('edit.failures.conflict'); // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics if ($this->mTitle->getNamespace() >= NS_MAIN && $this->mTitle->getNamespace() <= NS_CATEGORY_TALK) { $stats->increment('edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace()); } $wgOut->wrapWikiMsg('<h2>$1</h2>', "yourdiff"); $content1 = $this->toEditContent($this->textbox1); $content2 = $this->toEditContent($this->textbox2); $handler = ContentHandler::getForModelID($this->contentModel); $de = $handler->createDifferenceEngine($this->mArticle->getContext()); $de->setContent($content2, $content1); $de->showDiff($this->context->msg('yourtext')->parse(), $this->context->msg('storedversion')->text()); $wgOut->wrapWikiMsg('<h2>$1</h2>', "yourtext"); $this->showTextbox2(); } }
/** * @see Content::convert() * * This implementation provides lossless conversion between content models based * on TextContent. * * @param String $toModel the desired content model, use the CONTENT_MODEL_XXX flags. * @param String $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is * not allowed, full round-trip conversion is expected to work without losing information. * * @return Content|bool A content object with the content model $toModel, or false if * that conversion is not supported. */ public function convert($toModel, $lossy = '') { $converted = parent::convert($toModel, $lossy); if ($converted !== false) { return $converted; } $toHandler = ContentHandler::getForModelID($toModel); if ($toHandler instanceof TextContentHandler) { //NOTE: ignore content serialization format - it's just text anyway. $text = $this->getNativeData(); $converted = $toHandler->unserializeContent($text); } return $converted; }
/** * Get fields for search index * @since 1.28 * @return SearchIndexField[] Index field definitions for all content handlers */ public function getSearchIndexFields() { $models = ContentHandler::getContentModels(); $fields = []; foreach ($models as $model) { $handler = ContentHandler::getForModelID($model); $handlerFields = $handler->getFieldsForSearchIndex($this); foreach ($handlerFields as $fieldName => $fieldData) { if (empty($fields[$fieldName])) { $fields[$fieldName] = $fieldData; } else { // TODO: do we allow some clashes with the same type or reject all of them? $mergeDef = $fields[$fieldName]->merge($fieldData); if (!$mergeDef) { throw new InvalidArgumentException("Duplicate field {$fieldName} for model {$model}"); } $fields[$fieldName] = $mergeDef; } } } // Hook to allow extensions to produce search mapping fields Hooks::run('SearchIndexFields', [&$fields, $this]); return $fields; }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); if (is_null($params['text']) && is_null($params['appendtext']) && is_null($params['prependtext']) && $params['undo'] == 0) { $this->dieUsageMsg('missingtext'); } $pageObj = $this->getTitleOrPageId($params); $titleObj = $pageObj->getTitle(); $apiResult = $this->getResult(); if ($params['redirect']) { if ($params['prependtext'] === null && $params['appendtext'] === null && $params['section'] !== 'new') { $this->dieUsage('You have attempted to edit using the "redirect"-following' . ' mode, which must be used in conjuction with section=new, prependtext' . ', or appendtext.', 'redirect-appendonly'); } if ($titleObj->isRedirect()) { $oldTitle = $titleObj; $titles = Revision::newFromTitle($oldTitle, false, Revision::READ_LATEST)->getContent(Revision::FOR_THIS_USER, $user)->getRedirectChain(); // array_shift( $titles ); $redirValues = array(); /** @var $newTitle Title */ foreach ($titles as $id => $newTitle) { if (!isset($titles[$id - 1])) { $titles[$id - 1] = $oldTitle; } $redirValues[] = array('from' => $titles[$id - 1]->getPrefixedText(), 'to' => $newTitle->getPrefixedText()); $titleObj = $newTitle; } ApiResult::setIndexedTagName($redirValues, 'r'); $apiResult->addValue(null, 'redirects', $redirValues); // Since the page changed, update $pageObj $pageObj = WikiPage::factory($titleObj); } } if (!isset($params['contentmodel']) || $params['contentmodel'] == '') { $contentHandler = $pageObj->getContentHandler(); } else { $contentHandler = ContentHandler::getForModelID($params['contentmodel']); } $name = $titleObj->getPrefixedDBkey(); $model = $contentHandler->getModelID(); if ($contentHandler->supportsDirectApiEditing() === false) { $this->dieUsage("Direct editing via API is not supported for content model {$model} used by {$name}", 'no-direct-editing'); } if (!isset($params['contentformat']) || $params['contentformat'] == '') { $params['contentformat'] = $contentHandler->getDefaultFormat(); } $contentFormat = $params['contentformat']; if (!$contentHandler->isSupportedFormat($contentFormat)) { $this->dieUsage("The requested format {$contentFormat} is not supported for content model " . " {$model} used by {$name}", 'badformat'); } if ($params['createonly'] && $titleObj->exists()) { $this->dieUsageMsg('createonly-exists'); } if ($params['nocreate'] && !$titleObj->exists()) { $this->dieUsageMsg('nocreate-missing'); } // Now let's check whether we're even allowed to do this $errors = $titleObj->getUserPermissionsErrors('edit', $user); if (!$titleObj->exists()) { $errors = array_merge($errors, $titleObj->getUserPermissionsErrors('create', $user)); } if (count($errors)) { if (is_array($errors[0])) { switch ($errors[0][0]) { case 'blockedtext': $this->dieUsage('You have been blocked from editing', 'blocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); break; case 'autoblockedtext': $this->dieUsage('Your IP address has been blocked automatically, because it was used by a blocked user', 'autoblocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); break; default: $this->dieUsageMsg($errors[0]); } } else { $this->dieUsageMsg($errors[0]); } } $toMD5 = $params['text']; if (!is_null($params['appendtext']) || !is_null($params['prependtext'])) { $content = $pageObj->getContent(); if (!$content) { if ($titleObj->getNamespace() == NS_MEDIAWIKI) { # If this is a MediaWiki:x message, then load the messages # and return the message value for x. $text = $titleObj->getDefaultMessageText(); if ($text === false) { $text = ''; } try { $content = ContentHandler::makeContent($text, $this->getTitle()); } catch (MWContentSerializationException $ex) { $this->dieUsage($ex->getMessage(), 'parseerror'); return; } } else { # Otherwise, make a new empty content. $content = $contentHandler->makeEmptyContent(); } } // @todo Add support for appending/prepending to the Content interface if (!$content instanceof TextContent) { $mode = $contentHandler->getModelID(); $this->dieUsage("Can't append to pages using content model {$mode}", 'appendnotsupported'); } if (!is_null($params['section'])) { if (!$contentHandler->supportsSections()) { $modelName = $contentHandler->getModelID(); $this->dieUsage("Sections are not supported for this content model: {$modelName}.", 'sectionsnotsupported'); } if ($params['section'] == 'new') { // DWIM if they're trying to prepend/append to a new section. $content = null; } else { // Process the content for section edits $section = $params['section']; $content = $content->getSection($section); if (!$content) { $this->dieUsage("There is no section {$section}.", 'nosuchsection'); } } } if (!$content) { $text = ''; } else { $text = $content->serialize($contentFormat); } $params['text'] = $params['prependtext'] . $text . $params['appendtext']; $toMD5 = $params['prependtext'] . $params['appendtext']; } if ($params['undo'] > 0) { if ($params['undoafter'] > 0) { if ($params['undo'] < $params['undoafter']) { list($params['undo'], $params['undoafter']) = array($params['undoafter'], $params['undo']); } $undoafterRev = Revision::newFromId($params['undoafter']); } $undoRev = Revision::newFromId($params['undo']); if (is_null($undoRev) || $undoRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undo'])); } if ($params['undoafter'] == 0) { $undoafterRev = $undoRev->getPrevious(); } if (is_null($undoafterRev) || $undoafterRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undoafter'])); } if ($undoRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText())); } if ($undoafterRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText())); } $newContent = $contentHandler->getUndoContent($pageObj->getRevision(), $undoRev, $undoafterRev); if (!$newContent) { $this->dieUsageMsg('undo-failure'); } $params['text'] = $newContent->serialize($params['contentformat']); // If no summary was given and we only undid one rev, // use an autosummary if (is_null($params['summary']) && $titleObj->getNextRevisionID($undoafterRev->getID()) == $params['undo']) { $params['summary'] = wfMessage('undo-summary')->params($params['undo'], $undoRev->getUserText())->inContentLanguage()->text(); } } // See if the MD5 hash checks out if (!is_null($params['md5']) && md5($toMD5) !== $params['md5']) { $this->dieUsageMsg('hashcheckfailed'); } // EditPage wants to parse its stuff from a WebRequest // That interface kind of sucks, but it's workable $requestArray = array('wpTextbox1' => $params['text'], 'format' => $contentFormat, 'model' => $contentHandler->getModelID(), 'wpEditToken' => $params['token'], 'wpIgnoreBlankSummary' => true, 'wpIgnoreBlankArticle' => true, 'wpIgnoreSelfRedirect' => true, 'bot' => $params['bot']); if (!is_null($params['summary'])) { $requestArray['wpSummary'] = $params['summary']; } if (!is_null($params['sectiontitle'])) { $requestArray['wpSectionTitle'] = $params['sectiontitle']; } // TODO: Pass along information from 'undoafter' as well if ($params['undo'] > 0) { $requestArray['wpUndidRevision'] = $params['undo']; } // Watch out for basetimestamp == '' or '0' // It gets treated as NOW, almost certainly causing an edit conflict if ($params['basetimestamp'] !== null && (bool) $this->getMain()->getVal('basetimestamp')) { $requestArray['wpEdittime'] = $params['basetimestamp']; } else { $requestArray['wpEdittime'] = $pageObj->getTimestamp(); } if ($params['starttimestamp'] !== null) { $requestArray['wpStarttime'] = $params['starttimestamp']; } else { $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime } if ($params['minor'] || !$params['notminor'] && $user->getOption('minordefault')) { $requestArray['wpMinoredit'] = ''; } if ($params['recreate']) { $requestArray['wpRecreate'] = ''; } if (!is_null($params['section'])) { $section = $params['section']; if (!preg_match('/^((T-)?\\d+|new)$/', $section)) { $this->dieUsage("The section parameter must be a valid section id or 'new'", "invalidsection"); } $content = $pageObj->getContent(); if ($section !== '0' && $section != 'new' && (!$content || !$content->getSection($section))) { $this->dieUsage("There is no section {$section}.", 'nosuchsection'); } $requestArray['wpSection'] = $params['section']; } else { $requestArray['wpSection'] = ''; } $watch = $this->getWatchlistValue($params['watchlist'], $titleObj); // Deprecated parameters if ($params['watch']) { $this->logFeatureUsage('action=edit&watch'); $watch = true; } elseif ($params['unwatch']) { $this->logFeatureUsage('action=edit&unwatch'); $watch = false; } if ($watch) { $requestArray['wpWatchthis'] = ''; } // Apply change tags if (count($params['tags'])) { if ($user->isAllowed('applychangetags')) { $requestArray['wpChangeTags'] = implode(',', $params['tags']); } else { $this->dieUsage('You don\'t have permission to set change tags.', 'taggingnotallowed'); } } // Pass through anything else we might have been given, to support extensions // This is kind of a hack but it's the best we can do to make extensions work $requestArray += $this->getRequest()->getValues(); global $wgTitle, $wgRequest; $req = new DerivativeRequest($this->getRequest(), $requestArray, true); // Some functions depend on $wgTitle == $ep->mTitle // TODO: Make them not or check if they still do $wgTitle = $titleObj; $articleContext = new RequestContext(); $articleContext->setRequest($req); $articleContext->setWikiPage($pageObj); $articleContext->setUser($this->getUser()); /** @var $articleObject Article */ $articleObject = Article::newFromWikiPage($pageObj, $articleContext); $ep = new EditPage($articleObject); $ep->setApiEditOverride(true); $ep->setContextTitle($titleObj); $ep->importFormData($req); $content = $ep->textbox1; // The following is needed to give the hook the full content of the // new revision rather than just the current section. (Bug 52077) if (!is_null($params['section']) && $contentHandler->supportsSections() && $titleObj->exists()) { // If sectiontitle is set, use it, otherwise use the summary as the section title (for // backwards compatibility with old forms/bots). if ($ep->sectiontitle !== '') { $sectionTitle = $ep->sectiontitle; } else { $sectionTitle = $ep->summary; } $contentObj = $contentHandler->unserializeContent($content, $contentFormat); $fullContentObj = $articleObject->replaceSectionContent($params['section'], $contentObj, $sectionTitle); if ($fullContentObj) { $content = $fullContentObj->serialize($contentFormat); } else { // This most likely means we have an edit conflict which means that the edit // wont succeed anyway. $this->dieUsageMsg('editconflict'); } } // Run hooks // Handle APIEditBeforeSave parameters $r = array(); if (!Hooks::run('APIEditBeforeSave', array($ep, $content, &$r))) { if (count($r)) { $r['result'] = 'Failure'; $apiResult->addValue(null, $this->getModuleName(), $r); return; } $this->dieUsageMsg('hookaborted'); } // Do the actual save $oldRevId = $articleObject->getRevIdFetched(); $result = null; // Fake $wgRequest for some hooks inside EditPage // @todo FIXME: This interface SUCKS $oldRequest = $wgRequest; $wgRequest = $req; $status = $ep->attemptSave($result); $wgRequest = $oldRequest; switch ($status->value) { case EditPage::AS_HOOK_ERROR: case EditPage::AS_HOOK_ERROR_EXPECTED: if (isset($status->apiHookResult)) { $r = $status->apiHookResult; $r['result'] = 'Failure'; $apiResult->addValue(null, $this->getModuleName(), $r); return; } else { $this->dieUsageMsg('hookaborted'); } case EditPage::AS_PARSE_ERROR: $this->dieUsage($status->getMessage(), 'parseerror'); case EditPage::AS_IMAGE_REDIRECT_ANON: $this->dieUsageMsg('noimageredirect-anon'); case EditPage::AS_IMAGE_REDIRECT_LOGGED: $this->dieUsageMsg('noimageredirect-logged'); case EditPage::AS_SPAM_ERROR: $this->dieUsageMsg(array('spamdetected', $result['spam'])); case EditPage::AS_BLOCKED_PAGE_FOR_USER: $this->dieUsage('You have been blocked from editing', 'blocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED: case EditPage::AS_CONTENT_TOO_BIG: $this->dieUsageMsg(array('contenttoobig', $this->getConfig()->get('MaxArticleSize'))); case EditPage::AS_READ_ONLY_PAGE_ANON: $this->dieUsageMsg('noedit-anon'); case EditPage::AS_READ_ONLY_PAGE_LOGGED: $this->dieUsageMsg('noedit'); case EditPage::AS_READ_ONLY_PAGE: $this->dieReadOnly(); case EditPage::AS_RATE_LIMITED: $this->dieUsageMsg('actionthrottledtext'); case EditPage::AS_ARTICLE_WAS_DELETED: $this->dieUsageMsg('wasdeleted'); case EditPage::AS_NO_CREATE_PERMISSION: $this->dieUsageMsg('nocreate-loggedin'); case EditPage::AS_NO_CHANGE_CONTENT_MODEL: $this->dieUsageMsg('cantchangecontentmodel'); case EditPage::AS_BLANK_ARTICLE: $this->dieUsageMsg('blankpage'); case EditPage::AS_CONFLICT_DETECTED: $this->dieUsageMsg('editconflict'); case EditPage::AS_TEXTBOX_EMPTY: $this->dieUsageMsg('emptynewsection'); case EditPage::AS_CHANGE_TAG_ERROR: $this->dieStatus($status); case EditPage::AS_SUCCESS_NEW_ARTICLE: $r['new'] = true; // fall-through // fall-through case EditPage::AS_SUCCESS_UPDATE: $r['result'] = 'Success'; $r['pageid'] = intval($titleObj->getArticleID()); $r['title'] = $titleObj->getPrefixedText(); $r['contentmodel'] = $articleObject->getContentModel(); $newRevId = $articleObject->getLatest(); if ($newRevId == $oldRevId) { $r['nochange'] = true; } else { $r['oldrevid'] = intval($oldRevId); $r['newrevid'] = intval($newRevId); $r['newtimestamp'] = wfTimestamp(TS_ISO_8601, $pageObj->getTimestamp()); } break; case EditPage::AS_SUMMARY_NEEDED: // Shouldn't happen since we set wpIgnoreBlankSummary, but just in case $this->dieUsageMsg('summaryrequired'); case EditPage::AS_END: default: // $status came from WikiPage::doEdit() $errors = $status->getErrorsArray(); $this->dieUsageMsg($errors[0]); // TODO: Add new errors to message map break; } $apiResult->addValue(null, $this->getModuleName(), $r); }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); if (is_null($params['text']) && is_null($params['appendtext']) && is_null($params['prependtext']) && $params['undo'] == 0) { $this->dieUsageMsg('missingtext'); } $pageObj = $this->getTitleOrPageId($params); $titleObj = $pageObj->getTitle(); $apiResult = $this->getResult(); if ($params['redirect']) { if ($titleObj->isRedirect()) { $oldTitle = $titleObj; $titles = Revision::newFromTitle($oldTitle, false, Revision::READ_LATEST)->getContent(Revision::FOR_THIS_USER, $user)->getRedirectChain(); // array_shift( $titles ); $redirValues = array(); /** @var $newTitle Title */ foreach ($titles as $id => $newTitle) { if (!isset($titles[$id - 1])) { $titles[$id - 1] = $oldTitle; } $redirValues[] = array('from' => $titles[$id - 1]->getPrefixedText(), 'to' => $newTitle->getPrefixedText()); $titleObj = $newTitle; } $apiResult->setIndexedTagName($redirValues, 'r'); $apiResult->addValue(null, 'redirects', $redirValues); // Since the page changed, update $pageObj $pageObj = WikiPage::factory($titleObj); } } if (!isset($params['contentmodel']) || $params['contentmodel'] == '') { $contentHandler = $pageObj->getContentHandler(); } else { $contentHandler = ContentHandler::getForModelID($params['contentmodel']); } // @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such if (!isset($params['contentformat']) || $params['contentformat'] == '') { $params['contentformat'] = $contentHandler->getDefaultFormat(); } $contentFormat = $params['contentformat']; if (!$contentHandler->isSupportedFormat($contentFormat)) { $name = $titleObj->getPrefixedDBkey(); $model = $contentHandler->getModelID(); $this->dieUsage("The requested format {$contentFormat} is not supported for content model " . " {$model} used by {$name}", 'badformat'); } if ($params['createonly'] && $titleObj->exists()) { $this->dieUsageMsg('createonly-exists'); } if ($params['nocreate'] && !$titleObj->exists()) { $this->dieUsageMsg('nocreate-missing'); } // Now let's check whether we're even allowed to do this $errors = $titleObj->getUserPermissionsErrors('edit', $user); if (!$titleObj->exists()) { $errors = array_merge($errors, $titleObj->getUserPermissionsErrors('create', $user)); } if (count($errors)) { $this->dieUsageMsg($errors[0]); } $toMD5 = $params['text']; if (!is_null($params['appendtext']) || !is_null($params['prependtext'])) { $content = $pageObj->getContent(); if (!$content) { if ($titleObj->getNamespace() == NS_MEDIAWIKI) { # If this is a MediaWiki:x message, then load the messages # and return the message value for x. $text = $titleObj->getDefaultMessageText(); if ($text === false) { $text = ''; } try { $content = ContentHandler::makeContent($text, $this->getTitle()); } catch (MWContentSerializationException $ex) { $this->dieUsage($ex->getMessage(), 'parseerror'); return; } } else { # Otherwise, make a new empty content. $content = $contentHandler->makeEmptyContent(); } } // @todo Add support for appending/prepending to the Content interface if (!$content instanceof TextContent) { $mode = $contentHandler->getModelID(); $this->dieUsage("Can't append to pages using content model {$mode}", 'appendnotsupported'); } if (!is_null($params['section'])) { if (!$contentHandler->supportsSections()) { $modelName = $contentHandler->getModelID(); $this->dieUsage("Sections are not supported for this content model: {$modelName}.", 'sectionsnotsupported'); } // Process the content for section edits $section = intval($params['section']); $content = $content->getSection($section); if (!$content) { $this->dieUsage("There is no section {$section}.", 'nosuchsection'); } } if (!$content) { $text = ''; } else { $text = $content->serialize($contentFormat); } $params['text'] = $params['prependtext'] . $text . $params['appendtext']; $toMD5 = $params['prependtext'] . $params['appendtext']; } if ($params['undo'] > 0) { if ($params['undoafter'] > 0) { if ($params['undo'] < $params['undoafter']) { list($params['undo'], $params['undoafter']) = array($params['undoafter'], $params['undo']); } $undoafterRev = Revision::newFromID($params['undoafter']); } $undoRev = Revision::newFromID($params['undo']); if (is_null($undoRev) || $undoRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undo'])); } if ($params['undoafter'] == 0) { $undoafterRev = $undoRev->getPrevious(); } if (is_null($undoafterRev) || $undoafterRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undoafter'])); } if ($undoRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText())); } if ($undoafterRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText())); } $newContent = $contentHandler->getUndoContent($pageObj->getRevision(), $undoRev, $undoafterRev); if (!$newContent) { $this->dieUsageMsg('undo-failure'); } $params['text'] = $newContent->serialize($params['contentformat']); // If no summary was given and we only undid one rev, // use an autosummary if (is_null($params['summary']) && $titleObj->getNextRevisionID($undoafterRev->getID()) == $params['undo']) { $params['summary'] = wfMessage('undo-summary', $params['undo'], $undoRev->getUserText())->inContentLanguage()->text(); } } // See if the MD5 hash checks out if (!is_null($params['md5']) && md5($toMD5) !== $params['md5']) { $this->dieUsageMsg('hashcheckfailed'); } // EditPage wants to parse its stuff from a WebRequest // That interface kind of sucks, but it's workable $requestArray = array('wpTextbox1' => $params['text'], 'format' => $contentFormat, 'model' => $contentHandler->getModelID(), 'wpEditToken' => $params['token'], 'wpIgnoreBlankSummary' => ''); if (!is_null($params['summary'])) { $requestArray['wpSummary'] = $params['summary']; } if (!is_null($params['sectiontitle'])) { $requestArray['wpSectionTitle'] = $params['sectiontitle']; } // TODO: Pass along information from 'undoafter' as well if ($params['undo'] > 0) { $requestArray['wpUndidRevision'] = $params['undo']; } // Watch out for basetimestamp == '' // wfTimestamp() treats it as NOW, almost certainly causing an edit conflict if (!is_null($params['basetimestamp']) && $params['basetimestamp'] != '') { $requestArray['wpEdittime'] = wfTimestamp(TS_MW, $params['basetimestamp']); } else { $requestArray['wpEdittime'] = $pageObj->getTimestamp(); } if (!is_null($params['starttimestamp']) && $params['starttimestamp'] != '') { $requestArray['wpStarttime'] = wfTimestamp(TS_MW, $params['starttimestamp']); } else { $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime } if ($params['minor'] || !$params['notminor'] && $user->getOption('minordefault')) { $requestArray['wpMinoredit'] = ''; } if ($params['recreate']) { $requestArray['wpRecreate'] = ''; } if (!is_null($params['section'])) { $section = intval($params['section']); if ($section == 0 && $params['section'] != '0' && $params['section'] != 'new') { $this->dieUsage("The section parameter must be set to an integer or 'new'", "invalidsection"); } $requestArray['wpSection'] = $params['section']; } else { $requestArray['wpSection'] = ''; } $watch = $this->getWatchlistValue($params['watchlist'], $titleObj); // Deprecated parameters if ($params['watch']) { $watch = true; } elseif ($params['unwatch']) { $watch = false; } if ($watch) { $requestArray['wpWatchthis'] = ''; } global $wgTitle, $wgRequest; $req = new DerivativeRequest($this->getRequest(), $requestArray, true); // Some functions depend on $wgTitle == $ep->mTitle // TODO: Make them not or check if they still do $wgTitle = $titleObj; $articleContext = new RequestContext(); $articleContext->setRequest($req); $articleContext->setWikiPage($pageObj); $articleContext->setUser($this->getUser()); /** @var $articleObject Article */ $articleObject = Article::newFromWikiPage($pageObj, $articleContext); $ep = new EditPage($articleObject); // allow editing of non-textual content. $ep->allowNonTextContent = true; $ep->setContextTitle($titleObj); $ep->importFormData($req); // Run hooks // Handle APIEditBeforeSave parameters $r = array(); if (!wfRunHooks('APIEditBeforeSave', array($ep, $ep->textbox1, &$r))) { if (count($r)) { $r['result'] = 'Failure'; $apiResult->addValue(null, $this->getModuleName(), $r); return; } else { $this->dieUsageMsg('hookaborted'); } } // Do the actual save $oldRevId = $articleObject->getRevIdFetched(); $result = null; // Fake $wgRequest for some hooks inside EditPage // @todo FIXME: This interface SUCKS $oldRequest = $wgRequest; $wgRequest = $req; $status = $ep->internalAttemptSave($result, $user->isAllowed('bot') && $params['bot']); $wgRequest = $oldRequest; global $wgMaxArticleSize; switch ($status->value) { case EditPage::AS_HOOK_ERROR: case EditPage::AS_HOOK_ERROR_EXPECTED: $this->dieUsageMsg('hookaborted'); case EditPage::AS_PARSE_ERROR: $this->dieUsage($status->getMessage(), 'parseerror'); case EditPage::AS_IMAGE_REDIRECT_ANON: $this->dieUsageMsg('noimageredirect-anon'); case EditPage::AS_IMAGE_REDIRECT_LOGGED: $this->dieUsageMsg('noimageredirect-logged'); case EditPage::AS_SPAM_ERROR: $this->dieUsageMsg(array('spamdetected', $result['spam'])); case EditPage::AS_BLOCKED_PAGE_FOR_USER: $this->dieUsageMsg('blockedtext'); case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED: case EditPage::AS_CONTENT_TOO_BIG: $this->dieUsageMsg(array('contenttoobig', $wgMaxArticleSize)); case EditPage::AS_READ_ONLY_PAGE_ANON: $this->dieUsageMsg('noedit-anon'); case EditPage::AS_READ_ONLY_PAGE_LOGGED: $this->dieUsageMsg('noedit'); case EditPage::AS_READ_ONLY_PAGE: $this->dieReadOnly(); case EditPage::AS_RATE_LIMITED: $this->dieUsageMsg('actionthrottledtext'); case EditPage::AS_ARTICLE_WAS_DELETED: $this->dieUsageMsg('wasdeleted'); case EditPage::AS_NO_CREATE_PERMISSION: $this->dieUsageMsg('nocreate-loggedin'); case EditPage::AS_BLANK_ARTICLE: $this->dieUsageMsg('blankpage'); case EditPage::AS_CONFLICT_DETECTED: $this->dieUsageMsg('editconflict'); // case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary // case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary case EditPage::AS_TEXTBOX_EMPTY: $this->dieUsageMsg('emptynewsection'); case EditPage::AS_SUCCESS_NEW_ARTICLE: $r['new'] = ''; // fall-through // fall-through case EditPage::AS_SUCCESS_UPDATE: $r['result'] = 'Success'; $r['pageid'] = intval($titleObj->getArticleID()); $r['title'] = $titleObj->getPrefixedText(); $r['contentmodel'] = $titleObj->getContentModel(); $newRevId = $articleObject->getLatest(); if ($newRevId == $oldRevId) { $r['nochange'] = ''; } else { $r['oldrevid'] = intval($oldRevId); $r['newrevid'] = intval($newRevId); $r['newtimestamp'] = wfTimestamp(TS_ISO_8601, $pageObj->getTimestamp()); } break; case EditPage::AS_SUMMARY_NEEDED: $this->dieUsageMsg('summaryrequired'); case EditPage::AS_END: default: // $status came from WikiPage::doEdit() $errors = $status->getErrorsArray(); $this->dieUsageMsg($errors[0]); // TODO: Add new errors to message map break; } $apiResult->addValue(null, $this->getModuleName(), $r); }
/** * Dumps a "<revision>" section on the output stream, with * data filled in from the given database row. * * @param object $row * @return string * @access private */ function writeRevision($row) { wfProfileIn(__METHOD__); $out = " <revision>\n"; $out .= " " . Xml::element('id', null, strval($row->rev_id)) . "\n"; if (isset($row->rev_parent_id) && $row->rev_parent_id) { $out .= " " . Xml::element('parentid', null, strval($row->rev_parent_id)) . "\n"; } $out .= $this->writeTimestamp($row->rev_timestamp); if (isset($row->rev_deleted) && $row->rev_deleted & Revision::DELETED_USER) { $out .= " " . Xml::element('contributor', array('deleted' => 'deleted')) . "\n"; } else { $out .= $this->writeContributor($row->rev_user, $row->rev_user_text); } if (isset($row->rev_minor_edit) && $row->rev_minor_edit) { $out .= " <minor/>\n"; } if (isset($row->rev_deleted) && $row->rev_deleted & Revision::DELETED_COMMENT) { $out .= " " . Xml::element('comment', array('deleted' => 'deleted')) . "\n"; } elseif ($row->rev_comment != '') { $out .= " " . Xml::elementClean('comment', array(), strval($row->rev_comment)) . "\n"; } if (isset($row->rev_content_model) && !is_null($row->rev_content_model)) { $content_model = strval($row->rev_content_model); } else { // probably using $wgContentHandlerUseDB = false; $title = Title::makeTitle($row->page_namespace, $row->page_title); $content_model = ContentHandler::getDefaultModelFor($title); } $content_handler = ContentHandler::getForModelID($content_model); if (isset($row->rev_content_format) && !is_null($row->rev_content_format)) { $content_format = strval($row->rev_content_format); } else { // probably using $wgContentHandlerUseDB = false; $content_format = $content_handler->getDefaultFormat(); } $text = ''; if (isset($row->rev_deleted) && $row->rev_deleted & Revision::DELETED_TEXT) { $out .= " " . Xml::element('text', array('deleted' => 'deleted')) . "\n"; } elseif (isset($row->old_text)) { // Raw text from the database may have invalid chars $text = strval(Revision::getRevisionText($row)); $text = $content_handler->exportTransform($text, $content_format); $out .= " " . Xml::elementClean('text', array('xml:space' => 'preserve', 'bytes' => intval($row->rev_len)), strval($text)) . "\n"; } else { // Stub output $out .= " " . Xml::element('text', array('id' => $row->rev_text_id, 'bytes' => intval($row->rev_len)), "") . "\n"; } if (isset($row->rev_sha1) && $row->rev_sha1 && !($row->rev_deleted & Revision::DELETED_TEXT)) { $out .= " " . Xml::element('sha1', null, strval($row->rev_sha1)) . "\n"; } else { $out .= " <sha1/>\n"; } $out .= " " . Xml::element('model', null, strval($content_model)) . "\n"; $out .= " " . Xml::element('format', null, strval($content_format)) . "\n"; wfRunHooks('XmlDumpWriterWriteRevision', array(&$this, &$out, $row, $text)); $out .= " </revision>\n"; wfProfileOut(__METHOD__); return $out; }
private function updateRevisionOrArchiveRows(DatabaseBase $dbw, $ids, $model, $table) { $prefix = $table === 'archive' ? 'ar' : 'rev'; $model_column = "{$prefix}_content_model"; $format_column = "{$prefix}_content_format"; $key = "{$prefix}_id"; $count = count($ids); $format = ContentHandler::getForModelID($model)->getDefaultFormat(); $this->output("Setting {$count} rows to {$model} / {$format}..."); $dbw->update($table, array($model_column => $model, $format_column => $format), array($key => $ids), __METHOD__); $this->output("done.\n"); }
/** * Return an applicable autosummary if one exists for the given edit. * @param string|null $oldtext the previous text of the page. * @param string|null $newtext The submitted text of the page. * @param int $flags bitmask: a bitmask of flags submitted for the edit. * @return string An appropriate autosummary, or an empty string. * * @deprecated since 1.21, use ContentHandler::getAutosummary() instead */ public static function getAutosummary( $oldtext, $newtext, $flags ) { // NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't. ContentHandler::deprecated( __METHOD__, '1.21' ); $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT ); $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext ); $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext ); return $handler->getAutosummary( $oldContent, $newContent, $flags ); }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); if ($user->isBot()) { // sanity $this->dieUsage('This interface is not supported for bots', 'botsnotsupported'); } $page = $this->getTitleOrPageId($params); $title = $page->getTitle(); if (!ContentHandler::getForModelID($params['contentmodel'])->isSupportedFormat($params['contentformat'])) { $this->dieUsage('Unsupported content model/format', 'badmodelformat'); } // Trim and fix newlines so the key SHA1's match (see RequestContext::getText()) $text = rtrim(str_replace("\r\n", "\n", $params['text'])); $textContent = ContentHandler::makeContent($text, $title, $params['contentmodel'], $params['contentformat']); $page = WikiPage::factory($title); if ($page->exists()) { // Page exists: get the merged content with the proposed change $baseRev = Revision::newFromPageId($page->getId(), $params['baserevid']); if (!$baseRev) { $this->dieUsage("No revision ID {$params['baserevid']}", 'missingrev'); } $currentRev = $page->getRevision(); if (!$currentRev) { $this->dieUsage("No current revision of page ID {$page->getId()}", 'missingrev'); } // Merge in the new version of the section to get the proposed version $editContent = $page->replaceSectionAtRev($params['section'], $textContent, $params['sectiontitle'], $baseRev->getId()); if (!$editContent) { $this->dieUsage('Could not merge updated section.', 'replacefailed'); } if ($currentRev->getId() == $baseRev->getId()) { // Base revision was still the latest; nothing to merge $content = $editContent; } else { // Merge the edit into the current version $baseContent = $baseRev->getContent(); $currentContent = $currentRev->getContent(); if (!$baseContent || !$currentContent) { $this->dieUsage("Missing content for page ID {$page->getId()}", 'missingrev'); } $handler = ContentHandler::getForModelID($baseContent->getModel()); $content = $handler->merge3($baseContent, $editContent, $currentContent); } } else { // New pages: use the user-provided content model $content = $textContent; } if (!$content) { // merge3() failed $this->getResult()->addValue(null, $this->getModuleName(), ['status' => 'editconflict']); return; } // The user will abort the AJAX request by pressing "save", so ignore that ignore_user_abort(true); // Use the master DB for fast blocking locks $dbw = wfGetDB(DB_MASTER); // Get a key based on the source text, format, and user preferences $key = self::getStashKey($title, $content, $user); // De-duplicate requests on the same key if ($user->pingLimiter('stashedit')) { $status = 'ratelimited'; } elseif ($dbw->lock($key, __METHOD__, 1)) { $status = self::parseAndStash($page, $content, $user); $dbw->unlock($key, __METHOD__); } else { $status = 'busy'; } $this->getStats()->increment("editstash.cache_stores.{$status}"); $this->getResult()->addValue(null, $this->getModuleName(), ['status' => $status]); }