/** * Extracts the title and reason from the request parameters and invokes * the local delete() function with these as arguments. It does not make use of * the delete function specified by Article.php. If the deletion succeeds, the * details of the article deleted and the reason for deletion are added to the * result object. */ public function execute() { $this->useTransactionalTimeLimit(); $params = $this->extractRequestParams(); $pageObj = $this->getTitleOrPageId($params, 'fromdbmaster'); if (!$pageObj->exists()) { $this->dieUsageMsg('notanarticle'); } $titleObj = $pageObj->getTitle(); $reason = $params['reason']; $user = $this->getUser(); // Check that the user is allowed to carry out the deletion $errors = $titleObj->getUserPermissionsErrors('delete', $user); if (count($errors)) { $this->dieUsageMsg($errors[0]); } // If change tagging was requested, check that the user is allowed to tag, // and the tags are valid if (count($params['tags'])) { $tagStatus = ChangeTags::canAddTagsAccompanyingChange($params['tags'], $user); if (!$tagStatus->isOK()) { $this->dieStatus($tagStatus); } } if ($titleObj->getNamespace() == NS_FILE) { $status = self::deleteFile($pageObj, $user, $params['oldimage'], $reason, false); } else { $status = self::delete($pageObj, $user, $reason); } if (is_array($status)) { $this->dieUsageMsg($status[0]); } if (!$status->isGood()) { $this->dieStatus($status); } // Deprecated parameters if ($params['watch']) { $this->logFeatureUsage('action=delete&watch'); $watch = 'watch'; } elseif ($params['unwatch']) { $this->logFeatureUsage('action=delete&unwatch'); $watch = 'unwatch'; } else { $watch = $params['watchlist']; } $this->setWatch($watch, $titleObj, 'watchdeletion'); // Apply change tags to the log entry, if requested if (count($params['tags'])) { ChangeTags::addTags($params['tags'], null, null, $status->value, null); } $r = array('title' => $titleObj->getPrefixedText(), 'reason' => $reason, 'logid' => $status->value); $this->getResult()->addValue(null, $this->getModuleName(), $r); }
public function execute() { $this->useTransactionalTimeLimit(); $params = $this->extractRequestParams(); $user = $this->getUser(); if (!$user->isAllowed('undelete')) { $this->dieUsageMsg('permdenied-undelete'); } if ($user->isBlocked()) { $this->dieBlocked($user->getBlock()); } $titleObj = Title::newFromText($params['title']); if (!$titleObj || $titleObj->isExternal()) { $this->dieUsageMsg(['invalidtitle', $params['title']]); } // Check if user can add tags if (!is_null($params['tags'])) { $ableToTag = ChangeTags::canAddTagsAccompanyingChange($params['tags'], $user); if (!$ableToTag->isOK()) { $this->dieStatus($ableToTag); } } // Convert timestamps if (!isset($params['timestamps'])) { $params['timestamps'] = []; } if (!is_array($params['timestamps'])) { $params['timestamps'] = [$params['timestamps']]; } foreach ($params['timestamps'] as $i => $ts) { $params['timestamps'][$i] = wfTimestamp(TS_MW, $ts); } $pa = new PageArchive($titleObj, $this->getConfig()); $retval = $pa->undelete(isset($params['timestamps']) ? $params['timestamps'] : [], $params['reason'], $params['fileids'], false, $user, $params['tags']); if (!is_array($retval)) { $this->dieUsageMsg('cannotundelete'); } if ($retval[1]) { Hooks::run('FileUndeleteComplete', [$titleObj, $params['fileids'], $this->getUser(), $params['reason']]); } $this->setWatch($params['watchlist'], $titleObj); $info['title'] = $titleObj->getPrefixedText(); $info['revisions'] = intval($retval[0]); $info['fileversions'] = intval($retval[1]); $info['reason'] = $retval[2]; $this->getResult()->addValue(null, $this->getModuleName(), $info); }
public function execute() { $this->useTransactionalTimeLimit(); $user = $this->getUser(); $params = $this->extractRequestParams(); $titleObj = $this->getRbTitle($params); $pageObj = WikiPage::factory($titleObj); $summary = $params['summary']; $details = []; // If change tagging was requested, check that the user is allowed to tag, // and the tags are valid if (count($params['tags'])) { $tagStatus = ChangeTags::canAddTagsAccompanyingChange($params['tags'], $user); if (!$tagStatus->isOK()) { $this->dieStatus($tagStatus); } } $retval = $pageObj->doRollback($this->getRbUser($params), $summary, $params['token'], $params['markbot'], $details, $user, $params['tags']); // We don't care about multiple errors, just report one of them if ($retval) { if (isset($retval[0][0]) && ($retval[0][0] == 'alreadyrolled' || $retval[0][0] == 'cantrollback')) { $error = $retval[0]; $userMessage = $this->msg($error[0], array_slice($error, 1)); // dieUsageMsg() doesn't support $extraData $errorCode = $error[0]; $errorInfo = isset(ApiBase::$messageMap[$errorCode]) ? ApiBase::$messageMap[$errorCode]['info'] : $errorCode; $this->dieUsage($errorInfo, $errorCode, 0, ['messageHtml' => $userMessage->parseAsBlock()]); } $this->dieUsageMsg(reset($retval)); } $watch = 'preferences'; if (isset($params['watchlist'])) { $watch = $params['watchlist']; } // Watch pages $this->setWatch($watch, $titleObj, 'watchrollback'); $info = ['title' => $titleObj->getPrefixedText(), 'pageid' => intval($details['current']->getPage()), 'summary' => $details['summary'], 'revid' => intval($details['newid']), 'old_revid' => intval($details['current']->getID()), 'last_revid' => intval($details['target']->getID())]; $oldUser = $details['current']->getUserText(Revision::FOR_THIS_USER); $lastUser = $details['target']->getUserText(Revision::FOR_THIS_USER); $diffUrl = $titleObj->getFullURL(['diff' => $info['revid'], 'oldid' => $info['old_revid'], 'diffonly' => '1']); $info['messageHtml'] = $this->msg('rollback-success-notify')->params($oldUser, $lastUser, $diffUrl)->parseAsBlock(); $this->getResult()->addValue(null, $this->getModuleName(), $info); }
/** * Unblocks the specified user or provides the reason the unblock failed. */ public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); if (is_null($params['id']) && is_null($params['user'])) { $this->dieUsageMsg('unblock-notarget'); } if (!is_null($params['id']) && !is_null($params['user'])) { $this->dieUsageMsg('unblock-idanduser'); } if (!$user->isAllowed('block')) { $this->dieUsageMsg('cantunblock'); } # bug 15810: blocked admins should have limited access here if ($user->isBlocked()) { $status = SpecialBlock::checkUnblockSelf($params['user'], $user); if ($status !== true) { $msg = $this->parseMsg($status); $this->dieUsage($msg['info'], $msg['code'], 0, ['blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock())]); } } // Check if user can add tags if (!is_null($params['tags'])) { $ableToTag = ChangeTags::canAddTagsAccompanyingChange($params['tags'], $user); if (!$ableToTag->isOK()) { $this->dieStatus($ableToTag); } } $data = ['Target' => is_null($params['id']) ? $params['user'] : "******", 'Reason' => $params['reason'], 'Tags' => $params['tags']]; $block = Block::newFromTarget($data['Target']); $retval = SpecialUnblock::processUnblock($data, $this->getContext()); if ($retval !== true) { $this->dieUsageMsg($retval[0]); } $res['id'] = $block->getId(); $target = $block->getType() == Block::TYPE_AUTO ? '' : $block->getTarget(); $res['user'] = $target instanceof User ? $target->getName() : $target; $res['userid'] = $target instanceof User ? $target->getId() : 0; $res['reason'] = $params['reason']; $this->getResult()->addValue(null, $this->getModuleName(), $res); }
public function execute() { $this->useTransactionalTimeLimit(); $user = $this->getUser(); $params = $this->extractRequestParams(); // WikiPage::doRollback needs a Web UI token, so get one of those if we // validated based on an API rollback token. $token = $params['token']; if ($user->matchEditToken($token, 'rollback', $this->getRequest())) { $token = $this->getUser()->getEditToken($this->getWebUITokenSalt($params), $this->getRequest()); } $titleObj = $this->getRbTitle($params); $pageObj = WikiPage::factory($titleObj); $summary = $params['summary']; $details = array(); // If change tagging was requested, check that the user is allowed to tag, // and the tags are valid if (count($params['tags'])) { $tagStatus = ChangeTags::canAddTagsAccompanyingChange($params['tags'], $user); if (!$tagStatus->isOK()) { $this->dieStatus($tagStatus); } } $retval = $pageObj->doRollback($this->getRbUser($params), $summary, $token, $params['markbot'], $details, $user); if ($retval) { // We don't care about multiple errors, just report one of them $this->dieUsageMsg(reset($retval)); } $watch = 'preferences'; if (isset($params['watchlist'])) { $watch = $params['watchlist']; } // Watch pages $this->setWatch($watch, $titleObj, 'watchrollback'); if (count($params['tags'])) { ChangeTags::addTags($params['tags'], null, intval($details['newid']), null, null); } $info = array('title' => $titleObj->getPrefixedText(), 'pageid' => intval($details['current']->getPage()), 'summary' => $details['summary'], 'revid' => intval($details['newid']), 'old_revid' => intval($details['current']->getID()), 'last_revid' => intval($details['target']->getID())); $this->getResult()->addValue(null, $this->getModuleName(), $info); }
/** * Patrols the article or provides the reason the patrol failed. */ public function execute() { $params = $this->extractRequestParams(); $this->requireOnlyOneParameter($params, 'rcid', 'revid'); if (isset($params['rcid'])) { $rc = RecentChange::newFromId($params['rcid']); if (!$rc) { $this->dieUsageMsg(['nosuchrcid', $params['rcid']]); } } else { $rev = Revision::newFromId($params['revid']); if (!$rev) { $this->dieUsageMsg(['nosuchrevid', $params['revid']]); } $rc = $rev->getRecentChange(); if (!$rc) { $this->dieUsage('The revision ' . $params['revid'] . " can't be patrolled as it's too old", 'notpatrollable'); } } $user = $this->getUser(); $tags = $params['tags']; // Check if user can add tags if (!is_null($tags)) { $ableToTag = ChangeTags::canAddTagsAccompanyingChange($tags, $user); if (!$ableToTag->isOK()) { $this->dieStatus($ableToTag); } } $retval = $rc->doMarkPatrolled($user, false, $tags); if ($retval) { $this->dieUsageMsg(reset($retval)); } $result = ['rcid' => intval($rc->getAttribute('rc_id'))]; ApiQueryBase::addTitleInfo($result, $rc->getTitle()); $this->getResult()->addValue(null, $this->getModuleName(), $result); }
/** * Perform the actual upload. Returns a suitable result array on success; * dies on failure. * * @param array $warnings Array of Api upload warnings * @return array */ protected function performUpload($warnings) { // Use comment as initial page text by default if (is_null($this->mParams['text'])) { $this->mParams['text'] = $this->mParams['comment']; } /** @var $file File */ $file = $this->mUpload->getLocalFile(); // For preferences mode, we want to watch if 'watchdefault' is set, // or if the *file* doesn't exist, and either 'watchuploads' or // 'watchcreations' is set. But getWatchlistValue()'s automatic // handling checks if the *title* exists or not, so we need to check // all three preferences manually. $watch = $this->getWatchlistValue($this->mParams['watchlist'], $file->getTitle(), 'watchdefault'); if (!$watch && $this->mParams['watchlist'] == 'preferences' && !$file->exists()) { $watch = $this->getWatchlistValue('preferences', $file->getTitle(), 'watchuploads') || $this->getWatchlistValue('preferences', $file->getTitle(), 'watchcreations'); } // Deprecated parameters if ($this->mParams['watch']) { $watch = true; } if ($this->mParams['tags']) { $status = ChangeTags::canAddTagsAccompanyingChange($this->mParams['tags'], $this->getUser()); if (!$status->isOK()) { $this->dieStatus($status); } } // No errors, no warnings: do the upload if ($this->mParams['async']) { $progress = UploadBase::getSessionStatus($this->getUser(), $this->mParams['filekey']); if ($progress && $progress['result'] === 'Poll') { $this->dieUsage('Upload from stash already in progress.', 'publishfailed'); } UploadBase::setSessionStatus($this->getUser(), $this->mParams['filekey'], ['result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood()]); JobQueueGroup::singleton()->push(new PublishStashedFileJob(Title::makeTitle(NS_FILE, $this->mParams['filename']), ['filename' => $this->mParams['filename'], 'filekey' => $this->mParams['filekey'], 'comment' => $this->mParams['comment'], 'tags' => $this->mParams['tags'], 'text' => $this->mParams['text'], 'watch' => $watch, 'session' => $this->getContext()->exportSession()])); $result['result'] = 'Poll'; $result['stage'] = 'queued'; } else { /** @var $status Status */ $status = $this->mUpload->performUpload($this->mParams['comment'], $this->mParams['text'], $watch, $this->getUser(), $this->mParams['tags']); if (!$status->isGood()) { $error = $status->getErrorsArray(); ApiResult::setIndexedTagName($error, 'error'); $this->dieUsage('An internal error occurred', 'internal-error', 0, $error); } $result['result'] = 'Success'; } $result['filename'] = $file->getName(); if ($warnings && count($warnings) > 0) { $result['warnings'] = $warnings; } return $result; }
/** * Attempt submission (no UI) * * @param array $result Array to add statuses to, currently with the * possible keys: * - spam (string): Spam string from content if any spam is detected by * matchSpamRegex. * - sectionanchor (string): Section anchor for a section save. * - nullEdit (boolean): Set if doEditContent is OK. True if null edit, * false otherwise. * - redirect (bool): Set if doEditContent is OK. True if resulting * revision is a redirect. * @param bool $bot True if edit is being made under the bot right. * * @return Status Status object, possibly with a message, but always with * one of the AS_* constants in $status->value, * * @todo FIXME: This interface is TERRIBLE, but hard to get rid of due to * various error display idiosyncrasies. There are also lots of cases * where error metadata is set in the object and retrieved later instead * of being returned, e.g. AS_CONTENT_TOO_BIG and * AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some * time. */ function internalAttemptSave(&$result, $bot = false) { global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize; global $wgContentHandlerUseDB; $status = Status::newGood(); if (!Hooks::run('EditPage::attemptSave', array($this))) { wfDebug("Hook 'EditPage::attemptSave' aborted article saving\n"); $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; return $status; } $spam = $wgRequest->getText('wpAntispam'); if ($spam !== '') { wfDebugLog('SimpleAntiSpam', $wgUser->getName() . ' editing "' . $this->mTitle->getPrefixedText() . '" submitted bogus field "' . $spam . '"'); $status->fatal('spamprotectionmatch', false); $status->value = self::AS_SPAM_ERROR; return $status; } try { # Construct Content object $textbox_content = $this->toEditContent($this->textbox1); } catch (MWContentSerializationException $ex) { $status->fatal('content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage()); $status->value = self::AS_PARSE_ERROR; return $status; } # Check image redirect if ($this->mTitle->getNamespace() == NS_FILE && $textbox_content->isRedirect() && !$wgUser->isAllowed('upload')) { $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; $status->setResult(false, $code); return $status; } # Check for spam $match = self::matchSummarySpamRegex($this->summary); if ($match === false && $this->section == 'new') { # $wgSpamRegex is enforced on this new heading/summary because, unlike # regular summaries, it is added to the actual wikitext. if ($this->sectiontitle !== '') { # This branch is taken when the API is used with the 'sectiontitle' parameter. $match = self::matchSpamRegex($this->sectiontitle); } else { # This branch is taken when the "Add Topic" user interface is used, or the API # is used with the 'summary' parameter. $match = self::matchSpamRegex($this->summary); } } if ($match === false) { $match = self::matchSpamRegex($this->textbox1); } if ($match !== false) { $result['spam'] = $match; $ip = $wgRequest->getIP(); $pdbk = $this->mTitle->getPrefixedDBkey(); $match = str_replace("\n", '', $match); wfDebugLog('SpamRegex', "{$ip} spam regex hit [[{$pdbk}]]: \"{$match}\""); $status->fatal('spamprotectionmatch', $match); $status->value = self::AS_SPAM_ERROR; return $status; } if (!Hooks::run('EditFilter', array($this, $this->textbox1, $this->section, &$this->hookError, $this->summary))) { # Error messages etc. could be handled within the hook... $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; return $status; } elseif ($this->hookError != '') { # ...or the hook could be expecting us to produce an error $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR_EXPECTED; return $status; } if ($wgUser->isBlockedFrom($this->mTitle, false)) { // Auto-block user's IP if the account was "hard" blocked $wgUser->spreadAnyEditBlock(); # Check block state against master, thus 'false'. $status->setResult(false, self::AS_BLOCKED_PAGE_FOR_USER); return $status; } $this->kblength = (int) (strlen($this->textbox1) / 1024); if ($this->kblength > $wgMaxArticleSize) { // Error will be displayed by showEditForm() $this->tooBig = true; $status->setResult(false, self::AS_CONTENT_TOO_BIG); return $status; } if (!$wgUser->isAllowed('edit')) { if ($wgUser->isAnon()) { $status->setResult(false, self::AS_READ_ONLY_PAGE_ANON); return $status; } else { $status->fatal('readonlytext'); $status->value = self::AS_READ_ONLY_PAGE_LOGGED; return $status; } } $changingContentModel = false; if ($this->contentModel !== $this->mTitle->getContentModel()) { if (!$wgContentHandlerUseDB) { $status->fatal('editpage-cannot-use-custom-model'); $status->value = self::AS_CANNOT_USE_CUSTOM_MODEL; return $status; } elseif (!$wgUser->isAllowed('editcontentmodel')) { $status->setResult(false, self::AS_NO_CHANGE_CONTENT_MODEL); return $status; } $changingContentModel = true; $oldContentModel = $this->mTitle->getContentModel(); } if ($this->changeTags) { $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange($this->changeTags, $wgUser); if (!$changeTagsStatus->isOK()) { $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR; return $changeTagsStatus; } } if (wfReadOnly()) { $status->fatal('readonlytext'); $status->value = self::AS_READ_ONLY_PAGE; return $status; } if ($wgUser->pingLimiter() || $wgUser->pingLimiter('linkpurge', 0)) { $status->fatal('actionthrottledtext'); $status->value = self::AS_RATE_LIMITED; return $status; } # If the article has been deleted while editing, don't save it without # confirmation if ($this->wasDeletedSinceLastEdit() && !$this->recreate) { $status->setResult(false, self::AS_ARTICLE_WAS_DELETED); return $status; } # Load the page data from the master. If anything changes in the meantime, # we detect it by using page_latest like a token in a 1 try compare-and-swap. $this->page->loadPageData('fromdbmaster'); $new = !$this->page->exists(); if ($new) { // Late check for create permission, just in case *PARANOIA* if (!$this->mTitle->userCan('create', $wgUser)) { $status->fatal('nocreatetext'); $status->value = self::AS_NO_CREATE_PERMISSION; wfDebug(__METHOD__ . ": no create permission\n"); return $status; } // Don't save a new page if it's blank or if it's a MediaWiki: // message with content equivalent to default (allow empty pages // in this case to disable messages, see bug 50124) $defaultMessageText = $this->mTitle->getDefaultMessageText(); if ($this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false) { $defaultText = $defaultMessageText; } else { $defaultText = ''; } if (!$this->allowBlankArticle && $this->textbox1 === $defaultText) { $this->blankArticle = true; $status->fatal('blankarticle'); $status->setResult(false, self::AS_BLANK_ARTICLE); return $status; } if (!$this->runPostMergeFilters($textbox_content, $status, $wgUser)) { return $status; } $content = $textbox_content; $result['sectionanchor'] = ''; if ($this->section == 'new') { if ($this->sectiontitle !== '') { // Insert the section title above the content. $content = $content->addSectionHeader($this->sectiontitle); } elseif ($this->summary !== '') { // Insert the section title above the content. $content = $content->addSectionHeader($this->summary); } $this->summary = $this->newSectionSummary($result['sectionanchor']); } $status->value = self::AS_SUCCESS_NEW_ARTICLE; } else { # not $new # Article exists. Check for edit conflict. $this->page->clear(); # Force reload of dates, etc. $timestamp = $this->page->getTimestamp(); wfDebug("timestamp: {$timestamp}, edittime: {$this->edittime}\n"); if ($timestamp != $this->edittime) { $this->isConflict = true; if ($this->section == 'new') { if ($this->page->getUserText() == $wgUser->getName() && $this->page->getComment() == $this->newSectionSummary()) { // Probably a duplicate submission of a new comment. // This can happen when CDN resends a request after // a timeout but the first one actually went through. wfDebug(__METHOD__ . ": duplicate new section submission; trigger edit conflict!\n"); } else { // New comment; suppress conflict. $this->isConflict = false; wfDebug(__METHOD__ . ": conflict suppressed; new section\n"); } } elseif ($this->section == '' && Revision::userWasLastToEdit(DB_MASTER, $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime)) { # Suppress edit conflict with self, except for section edits where merging is required. wfDebug(__METHOD__ . ": Suppressing edit conflict, same user.\n"); $this->isConflict = false; } } // If sectiontitle is set, use it, otherwise use the summary as the section title. if ($this->sectiontitle !== '') { $sectionTitle = $this->sectiontitle; } else { $sectionTitle = $this->summary; } $content = null; if ($this->isConflict) { wfDebug(__METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'" . " (article time '{$timestamp}')\n"); $content = $this->page->replaceSectionContent($this->section, $textbox_content, $sectionTitle, $this->edittime); } else { wfDebug(__METHOD__ . ": getting section '{$this->section}'\n"); $content = $this->page->replaceSectionContent($this->section, $textbox_content, $sectionTitle); } if (is_null($content)) { wfDebug(__METHOD__ . ": activating conflict; section replace failed.\n"); $this->isConflict = true; $content = $textbox_content; // do not try to merge here! } elseif ($this->isConflict) { # Attempt merge if ($this->mergeChangesIntoContent($content)) { // Successful merge! Maybe we should tell the user the good news? $this->isConflict = false; wfDebug(__METHOD__ . ": Suppressing edit conflict, successful merge.\n"); } else { $this->section = ''; $this->textbox1 = ContentHandler::getContentText($content); wfDebug(__METHOD__ . ": Keeping edit conflict, failed merge.\n"); } } if ($this->isConflict) { $status->setResult(false, self::AS_CONFLICT_DETECTED); return $status; } if (!$this->runPostMergeFilters($content, $status, $wgUser)) { return $status; } if ($this->section == 'new') { // Handle the user preference to force summaries here if (!$this->allowBlankSummary && trim($this->summary) == '') { $this->missingSummary = true; $status->fatal('missingsummary'); // or 'missingcommentheader' if $section == 'new'. Blegh $status->value = self::AS_SUMMARY_NEEDED; return $status; } // Do not allow the user to post an empty comment if ($this->textbox1 == '') { $this->missingComment = true; $status->fatal('missingcommenttext'); $status->value = self::AS_TEXTBOX_EMPTY; return $status; } } elseif (!$this->allowBlankSummary && !$content->equals($this->getOriginalContent($wgUser)) && !$content->isRedirect() && md5($this->summary) == $this->autoSumm) { $this->missingSummary = true; $status->fatal('missingsummary'); $status->value = self::AS_SUMMARY_NEEDED; return $status; } # All's well $sectionanchor = ''; if ($this->section == 'new') { $this->summary = $this->newSectionSummary($sectionanchor); } elseif ($this->section != '') { # Try to get a section anchor from the section source, redirect # to edited section if header found. # XXX: Might be better to integrate this into Article::replaceSection # for duplicate heading checking and maybe parsing. $hasmatch = preg_match("/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches); # We can't deal with anchors, includes, html etc in the header for now, # headline would need to be parsed to improve this. if ($hasmatch && strlen($matches[2]) > 0) { $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText($matches[2]); } } $result['sectionanchor'] = $sectionanchor; // Save errors may fall down to the edit form, but we've now // merged the section into full text. Clear the section field // so that later submission of conflict forms won't try to // replace that into a duplicated mess. $this->textbox1 = $this->toEditText($content); $this->section = ''; $status->value = self::AS_SUCCESS_UPDATE; } if (!$this->allowSelfRedirect && $content->isRedirect() && $content->getRedirectTarget()->equals($this->getTitle())) { // If the page already redirects to itself, don't warn. $currentTarget = $this->getCurrentContent()->getRedirectTarget(); if (!$currentTarget || !$currentTarget->equals($this->getTitle())) { $this->selfRedirect = true; $status->fatal('selfredirect'); $status->value = self::AS_SELF_REDIRECT; return $status; } } // Check for length errors again now that the section is merged in $this->kblength = (int) (strlen($this->toEditText($content)) / 1024); if ($this->kblength > $wgMaxArticleSize) { $this->tooBig = true; $status->setResult(false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED); return $status; } $flags = EDIT_AUTOSUMMARY | ($new ? EDIT_NEW : EDIT_UPDATE) | ($this->minoredit && !$this->isNew ? EDIT_MINOR : 0) | ($bot ? EDIT_FORCE_BOT : 0); $doEditStatus = $this->page->doEditContent($content, $this->summary, $flags, false, $wgUser, $content->getDefaultFormat(), $this->changeTags); if (!$doEditStatus->isOK()) { // Failure from doEdit() // Show the edit conflict page for certain recognized errors from doEdit(), // but don't show it for errors from extension hooks $errors = $doEditStatus->getErrorsArray(); if (in_array($errors[0][0], array('edit-gone-missing', 'edit-conflict', 'edit-already-exists'))) { $this->isConflict = true; // Destroys data doEdit() put in $status->value but who cares $doEditStatus->value = self::AS_END; } return $doEditStatus; } $result['nullEdit'] = $doEditStatus->hasMessage('edit-no-change'); if ($result['nullEdit']) { // We don't know if it was a null edit until now, so increment here $wgUser->pingLimiter('linkpurge'); } $result['redirect'] = $content->isRedirect(); $this->updateWatchlist(); // If the content model changed, add a log entry if ($changingContentModel) { $this->addContentModelChangeLogEntry($wgUser, $oldContentModel, $this->contentModel, $this->summary); } return $status; }
/** * Using the settings determine the value for the given parameter * * @param string $paramName Parameter name * @param array|mixed $paramSettings Default value or an array of settings * using PARAM_* constants. * @param bool $parseLimit Parse limit? * @return mixed Parameter value */ protected function getParameterFromSettings($paramName, $paramSettings, $parseLimit) { // Some classes may decide to change parameter names $encParamName = $this->encodeParamName($paramName); if (!is_array($paramSettings)) { $default = $paramSettings; $multi = false; $type = gettype($paramSettings); $dupes = false; $deprecated = false; $required = false; } else { $default = isset($paramSettings[self::PARAM_DFLT]) ? $paramSettings[self::PARAM_DFLT] : null; $multi = isset($paramSettings[self::PARAM_ISMULTI]) ? $paramSettings[self::PARAM_ISMULTI] : false; $type = isset($paramSettings[self::PARAM_TYPE]) ? $paramSettings[self::PARAM_TYPE] : null; $dupes = isset($paramSettings[self::PARAM_ALLOW_DUPLICATES]) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false; $deprecated = isset($paramSettings[self::PARAM_DEPRECATED]) ? $paramSettings[self::PARAM_DEPRECATED] : false; $required = isset($paramSettings[self::PARAM_REQUIRED]) ? $paramSettings[self::PARAM_REQUIRED] : false; // When type is not given, and no choices, the type is the same as $default if (!isset($type)) { if (isset($default)) { $type = gettype($default); } else { $type = 'NULL'; // allow everything } } } if ($type == 'boolean') { if (isset($default) && $default !== false) { // Having a default value of anything other than 'false' is not allowed ApiBase::dieDebug(__METHOD__, "Boolean param {$encParamName}'s default is set to '{$default}'. " . 'Boolean parameters must default to false.'); } $value = $this->getMain()->getCheck($encParamName); } elseif ($type == 'upload') { if (isset($default)) { // Having a default value is not allowed ApiBase::dieDebug(__METHOD__, "File upload param {$encParamName}'s default is set to " . "'{$default}'. File upload parameters may not have a default."); } if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } $value = $this->getMain()->getUpload($encParamName); if (!$value->exists()) { // This will get the value without trying to normalize it // (because trying to normalize a large binary file // accidentally uploaded as a field fails spectacularly) $value = $this->getMain()->getRequest()->unsetVal($encParamName); if ($value !== null) { $this->dieUsage("File upload param {$encParamName} is not a file upload; " . 'be sure to use multipart/form-data for your POST and include ' . 'a filename in the Content-Disposition header.', "badupload_{$encParamName}"); } } } else { $value = $this->getMain()->getVal($encParamName, $default); if (isset($value) && $type == 'namespace') { $type = MWNamespace::getValidNamespaces(); } if (isset($value) && $type == 'submodule') { if (isset($paramSettings[self::PARAM_SUBMODULE_MAP])) { $type = array_keys($paramSettings[self::PARAM_SUBMODULE_MAP]); } else { $type = $this->getModuleManager()->getNames($paramName); } } } if (isset($value) && ($multi || is_array($type))) { $value = $this->parseMultiValue($encParamName, $value, $multi, is_array($type) ? $type : null); } // More validation only when choices were not given // choices were validated in parseMultiValue() if (isset($value)) { if (!is_array($type)) { switch ($type) { case 'NULL': // nothing to do break; case 'string': case 'text': case 'password': if ($required && $value === '') { $this->dieUsageMsg(['missingparam', $paramName]); } break; case 'integer': // Force everything using intval() and optionally validate limits $min = isset($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : null; $max = isset($paramSettings[self::PARAM_MAX]) ? $paramSettings[self::PARAM_MAX] : null; $enforceLimits = isset($paramSettings[self::PARAM_RANGE_ENFORCE]) ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false; if (is_array($value)) { $value = array_map('intval', $value); if (!is_null($min) || !is_null($max)) { foreach ($value as &$v) { $this->validateLimit($paramName, $v, $min, $max, null, $enforceLimits); } } } else { $value = intval($value); if (!is_null($min) || !is_null($max)) { $this->validateLimit($paramName, $value, $min, $max, null, $enforceLimits); } } break; case 'limit': if (!$parseLimit) { // Don't do any validation whatsoever break; } if (!isset($paramSettings[self::PARAM_MAX]) || !isset($paramSettings[self::PARAM_MAX2])) { ApiBase::dieDebug(__METHOD__, "MAX1 or MAX2 are not defined for the limit {$encParamName}"); } if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } $min = isset($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : 0; if ($value == 'max') { $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX]; $this->getResult()->addParsedLimit($this->getModuleName(), $value); } else { $value = intval($value); $this->validateLimit($paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2]); } break; case 'boolean': if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } break; case 'timestamp': if (is_array($value)) { foreach ($value as $key => $val) { $value[$key] = $this->validateTimestamp($val, $encParamName); } } else { $value = $this->validateTimestamp($value, $encParamName); } break; case 'user': if (is_array($value)) { foreach ($value as $key => $val) { $value[$key] = $this->validateUser($val, $encParamName); } } else { $value = $this->validateUser($value, $encParamName); } break; case 'upload': // nothing to do break; case 'tags': // If change tagging was requested, check that the tags are valid. if (!is_array($value) && !$multi) { $value = [$value]; } $tagsStatus = ChangeTags::canAddTagsAccompanyingChange($value); if (!$tagsStatus->isGood()) { $this->dieStatus($tagsStatus); } break; default: ApiBase::dieDebug(__METHOD__, "Param {$encParamName}'s type is unknown - {$type}"); } } // Throw out duplicates if requested if (!$dupes && is_array($value)) { $value = array_unique($value); } // Set a warning if a deprecated parameter has been passed if ($deprecated && $value !== false) { $this->setWarning("The {$encParamName} parameter has been deprecated."); $feature = $encParamName; $m = $this; while (!$m->isMain()) { $p = $m->getParent(); $name = $m->getModuleName(); $param = $p->encodeParamName($p->getModuleManager()->getModuleGroup($name)); $feature = "{$param}={$name}&{$feature}"; $m = $p; } $this->logFeatureUsage($feature); } } elseif ($required) { $this->dieUsageMsg(['missingparam', $paramName]); } return $value; }
/** * Do the upload. * Checks are made in SpecialUpload::execute() */ protected function processUpload() { // Fetch the file if required $status = $this->mUpload->fetchFile(); if (!$status->isOK()) { $this->showUploadError($this->getOutput()->parse($status->getWikiText())); return; } if (!Hooks::run('UploadForm:BeforeProcessing', [&$this])) { wfDebug("Hook 'UploadForm:BeforeProcessing' broke processing the file.\n"); // This code path is deprecated. If you want to break upload processing // do so by hooking into the appropriate hooks in UploadBase::verifyUpload // and UploadBase::verifyFile. // If you use this hook to break uploading, the user will be returned // an empty form with no error message whatsoever. return; } // Upload verification $details = $this->mUpload->verifyUpload(); if ($details['status'] != UploadBase::OK) { $this->processVerificationError($details); return; } // Verify permissions for this title $permErrors = $this->mUpload->verifyTitlePermissions($this->getUser()); if ($permErrors !== true) { $code = array_shift($permErrors[0]); $this->showRecoverableUploadError($this->msg($code, $permErrors[0])->parse()); return; } $this->mLocalFile = $this->mUpload->getLocalFile(); // Check warnings if necessary if (!$this->mIgnoreWarning) { $warnings = $this->mUpload->checkWarnings(); if ($this->showUploadWarning($warnings)) { return; } } // This is as late as we can throttle, after expected issues have been handled if (UploadBase::isThrottled($this->getUser())) { $this->showRecoverableUploadError($this->msg('actionthrottledtext')->escaped()); return; } // Get the page text if this is not a reupload if (!$this->mForReUpload) { $pageText = self::getInitialPageText($this->mComment, $this->mLicense, $this->mCopyrightStatus, $this->mCopyrightSource, $this->getConfig()); } else { $pageText = false; } $changeTags = $this->getRequest()->getVal('wpChangeTags'); if (is_null($changeTags) || $changeTags === '') { $changeTags = []; } else { $changeTags = array_filter(array_map('trim', explode(',', $changeTags))); } if ($changeTags) { $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange($changeTags, $this->getUser()); if (!$changeTagsStatus->isOK()) { $this->showUploadError($this->getOutput()->parse($changeTagsStatus->getWikiText())); return; } } $status = $this->mUpload->performUpload($this->mComment, $pageText, $this->mWatchthis, $this->getUser(), $changeTags); if (!$status->isGood()) { $this->showRecoverableUploadError($this->getOutput()->parse($status->getWikiText())); return; } // Success, redirect to description page $this->mUploadSuccessful = true; Hooks::run('SpecialUploadComplete', [&$this]); $this->getOutput()->redirect($this->mLocalFile->getTitle()->getFullURL()); }
public function execute() { $this->useTransactionalTimeLimit(); $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 ($params['undo'] > 0) { // allow undo via api } elseif ($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']) { $watch = true; } elseif ($params['unwatch']) { $watch = false; } if ($watch) { $requestArray['wpWatchthis'] = ''; } // Apply change tags if (count($params['tags'])) { $tagStatus = ChangeTags::canAddTagsAccompanyingChange($params['tags'], $user); if ($tagStatus->isOk()) { $requestArray['wpChangeTags'] = implode(',', $params['tags']); } else { $this->dieStatus($tagStatus); } } // 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() { global $wgContLang; $params = $this->extractRequestParams(); $pageObj = $this->getTitleOrPageId($params, 'fromdbmaster'); $titleObj = $pageObj->getTitle(); $errors = $titleObj->getUserPermissionsErrors('protect', $this->getUser()); if ($errors) { // We don't care about multiple errors, just report one of them $this->dieUsageMsg(reset($errors)); } $user = $this->getUser(); $tags = $params['tags']; // Check if user can add tags if (!is_null($tags)) { $ableToTag = ChangeTags::canAddTagsAccompanyingChange($tags, $user); if (!$ableToTag->isOK()) { $this->dieStatus($ableToTag); } } $expiry = (array) $params['expiry']; if (count($expiry) != count($params['protections'])) { if (count($expiry) == 1) { $expiry = array_fill(0, count($params['protections']), $expiry[0]); } else { $this->dieUsageMsg(['toofewexpiries', count($expiry), count($params['protections'])]); } } $restrictionTypes = $titleObj->getRestrictionTypes(); $protections = []; $expiryarray = []; $resultProtections = []; foreach ($params['protections'] as $i => $prot) { $p = explode('=', $prot); $protections[$p[0]] = $p[1] == 'all' ? '' : $p[1]; if ($titleObj->exists() && $p[0] == 'create') { $this->dieUsageMsg('create-titleexists'); } if (!$titleObj->exists() && $p[0] != 'create') { $this->dieUsageMsg('missingtitle-createonly'); } if (!in_array($p[0], $restrictionTypes) && $p[0] != 'create') { $this->dieUsageMsg(['protect-invalidaction', $p[0]]); } if (!in_array($p[1], $this->getConfig()->get('RestrictionLevels')) && $p[1] != 'all') { $this->dieUsageMsg(['protect-invalidlevel', $p[1]]); } if (wfIsInfinity($expiry[$i])) { $expiryarray[$p[0]] = 'infinity'; } else { $exp = strtotime($expiry[$i]); if ($exp < 0 || !$exp) { $this->dieUsageMsg(['invalidexpiry', $expiry[$i]]); } $exp = wfTimestamp(TS_MW, $exp); if ($exp < wfTimestampNow()) { $this->dieUsageMsg(['pastexpiry', $expiry[$i]]); } $expiryarray[$p[0]] = $exp; } $resultProtections[] = [$p[0] => $protections[$p[0]], 'expiry' => $wgContLang->formatExpiry($expiryarray[$p[0]], TS_ISO_8601, 'infinite')]; } $cascade = $params['cascade']; $watch = $params['watch'] ? 'watch' : $params['watchlist']; $this->setWatch($watch, $titleObj, 'watchdefault'); $status = $pageObj->doUpdateRestrictions($protections, $expiryarray, $cascade, $params['reason'], $user, $tags); if (!$status->isOK()) { $this->dieStatus($status); } $res = ['title' => $titleObj->getPrefixedText(), 'reason' => $params['reason']]; if ($cascade) { $res['cascade'] = true; } $res['protections'] = $resultProtections; $result = $this->getResult(); ApiResult::setIndexedTagName($res['protections'], 'protection'); $result->addValue(null, $this->getModuleName(), $res); }