Beispiel #1
0
 /**
  * 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);
 }
Beispiel #2
0
 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);
 }
Beispiel #3
0
 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);
 }
Beispiel #4
0
 /**
  * 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);
 }
Beispiel #5
0
 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);
 }
Beispiel #6
0
 /**
  * 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);
 }
Beispiel #7
0
 /**
  * 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;
 }
Beispiel #8
0
 /**
  * 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;
 }
Beispiel #9
0
 /**
  * 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;
 }
Beispiel #10
0
 /**
  * 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());
 }
Beispiel #11
0
 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);
 }
Beispiel #12
0
 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);
 }