protected function setUp() { parent::setUp(); global $wgLang; $this->setMwGlobals(array('wgLogTypes' => array('phpunit'), 'wgLogActionsHandlers' => array('phpunit/test' => 'LogFormatter', 'phpunit/param' => 'LogFormatter'), 'wgUser' => User::newFromName('Testuser'), 'wgExtensionMessagesFiles' => array('LogTests' => __DIR__ . '/LogTests.i18n.php'))); Language::getLocalisationCache()->recache($wgLang->getCode()); $this->user = User::newFromName('Testuser'); $this->title = Title::newMainPage(); $this->context = new RequestContext(); $this->context->setUser($this->user); $this->context->setTitle($this->title); $this->context->setLanguage($wgLang); }
protected function setUp() { parent::setUp(); global $wgLang; $this->setMwGlobals(['wgLogTypes' => ['phpunit'], 'wgLogActionsHandlers' => ['phpunit/test' => 'LogFormatter', 'phpunit/param' => 'LogFormatter'], 'wgUser' => User::newFromName('Testuser'), 'wgExtensionMessagesFiles' => ['LogTests' => __DIR__ . '/LogTests.i18n.php']]); Language::getLocalisationCache()->recache($wgLang->getCode()); $this->user = User::newFromName('Testuser'); $this->title = Title::newFromText('SomeTitle'); $this->target = Title::newFromText('TestTarget'); $this->context = new RequestContext(); $this->context->setUser($this->user); $this->context->setTitle($this->title); $this->context->setLanguage($wgLang); $this->user_comment = '<User comment about action>'; }
public function getTestContext(User $user) { $context = new RequestContext(); $context->setLanguage(Language::factory('en')); $context->setUser($user); return $context; }
public function getTestContext(User $user) { $context = new RequestContext(); $context->setLanguage('en'); $context->setUser($user); $title = Title::newFromText('RecentChanges', NS_SPECIAL); $context->setTitle($title); return $context; }
/** * @dataProvider getSlugs */ public function testGetMembersActionWithMultiUsers($slug) { $this->addSlugToCache($slug, array(array('slug' => $this->user->Slug, 'name' => $this->user->Title, 'pingedAt' => null, 'updateMeta' => false))); // set new user $user = new \stdClass(); $user->Slug = 'bobdoll'; $user->Title = 'Bob Doll'; // update logged-in user $this->requestContext->setUser($user); $this->request->addRouteParameters(array('slug' => $slug)); ob_start(); $this->controller->getMembersAction(); $output = ob_get_contents(); ob_end_clean(); $this->assertJsonStringEqualsJsonString($output, json_encode(array(array('slug' => $this->user->Slug, 'name' => $this->user->Title, 'pingedAt' => null, 'updateMeta' => false), array('slug' => $user->Slug, 'name' => $user->Title, 'pingedAt' => null, 'updateMeta' => false)))); }
/** * Make sure a nickname which is longer than $wgMaxSigChars * is not throwing a fatal error. * * Test specifications by Alexandre "ialex" Emsenhuber. * @todo give this test a real name explaining what is being tested here */ public function testBug41337() { // Set a low limit $this->setMwGlobals('wgMaxSigChars', 2); $user = $this->getMock('User'); $user->expects($this->any())->method('isAnon')->will($this->returnValue(false)); # Yeah foreach requires an array, not NULL =( $user->expects($this->any())->method('getEffectiveGroups')->will($this->returnValue([])); # The mocked user has a long nickname $user->expects($this->any())->method('getOption')->will($this->returnValueMap([['nickname', null, false, 'superlongnickname']])); # Forge a request to call the special page $context = new RequestContext(); $context->setRequest(new FauxRequest()); $context->setUser($user); $context->setTitle(Title::newFromText('Test')); # Do the call, should not spurt a fatal error. $special = new SpecialPreferences(); $special->setContext($context); $this->assertNull($special->execute([])); }
/** * @covers SpecialSearch::load * @dataProvider provideSearchOptionsTests * @param $requested Array Request parameters. For example array( 'ns5' => true, 'ns6' => true). NULL to use default options. * @param $userOptions Array User options to test with. For example array('searchNs5' => 1 );. NULL to use default options. * @param $expectedProfile An expected search profile name * @param $expectedNs Array Expected namespaces */ function testProfileAndNamespaceLoading($requested, $userOptions, $expectedProfile, $expectedNS, $message = 'Profile name and namespaces mismatches!') { $context = new RequestContext(); $context->setUser($this->newUserWithSearchNS($userOptions)); /* $context->setRequest( new FauxRequest( array( 'ns5'=>true, 'ns6'=>true, ) )); */ $context->setRequest(new FauxRequest($requested)); $search = new SpecialSearch(); $search->setContext($context); $search->load(); /** * Verify profile name and namespace in the same assertion to make * sure we will be able to fully compare the above code. PHPUnit stop * after an assertion fail. */ $this->assertEquals(array('ProfileName' => $expectedProfile, 'Namespaces' => $expectedNS), array('ProfileName' => $search->getProfile(), 'Namespaces' => $search->getNamespaces()), $message); }
/** * @dataProvider providerShowRedLinks */ public function testRedLinks($showRedLinks, $showRedLinksAnon, $username, $expected) { // set config variables, which we test here $values = array('wgMFShowRedLinks' => $showRedLinks, 'wgMFShowRedLinksAnon' => $showRedLinksAnon, 'wgMFEnableBeta' => true); $this->setMwGlobals($values); // create our specific user object $user = User::newFromName($username); $user->load(); // create a new RequestContext for this test case and set User and title $context = new RequestContext(); $context->setUser($user); $context->setTitle(Title::newFromText('Main_page')); // create SkinMinerva to test $skin = $this->getSkin(); $skin->setContext($context); // set the fake mobile mode MobileContext::singleton()->setMobileMode($this->getMode()); // test now $vars = $skin->getSkinConfigVariables(); $this->assertEquals($expected, $vars['wgMFShowRedLinks']); }
/** * Check, if context modules aren't arrays. They will be added as an array with modules to * to load, which doesn't allow arrays as values. */ public function testGetContextSpecificModules() { // try to cover all possible modules (maybe extent, if other modules added) $values = array('wgMFEnableBeta' => true); $this->setMwGlobals($values); // UTSysop will be a logged in user $user = User::newFromName('UTSysop'); $user->load(); // create a new RequestContext for this test case and set User and title $context = new RequestContext(); $context->setUser($user); // UTPage is an existing page in the main namespace $context->setTitle(Title::newFromText('UTPage')); MobileContext::singleton()->setMobileMode('alpha'); $skin = $this->getSkin(); $skin->setContext($context); $modules = $skin->getContextSpecificModules(); foreach ($modules as $module) { $this->assertFalse(is_array($module), 'Context specific modules can\'t be arrays.'); } }
/** * Return the text of a template, after recursively * replacing any variables or templates within the template. * * @param array $piece The parts of the template * $piece['title']: the title, i.e. the part before the | * $piece['parts']: the parameter array * $piece['lineStart']: whether the brace was at the start of a line * @param PPFrame $frame The current frame, contains template arguments * @throws MWException * @return string The text of the template * @private */ function braceSubstitution($piece, $frame) { wfProfileIn(__METHOD__); wfProfileIn(__METHOD__ . '-setup'); # Flags $found = false; # $text has been filled $nowiki = false; # wiki markup in $text should be escaped $isHTML = false; # $text is HTML, armour it against wikitext transformation $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered $isChildObj = false; # $text is a DOM node needing expansion in a child frame $isLocalObj = false; # $text is a DOM node needing expansion in the current frame # Title object, where $text came from $title = false; # $part1 is the bit before the first |, and must contain only title characters. # Various prefixes will be stripped from it later. $titleWithSpaces = $frame->expand($piece['title']); $part1 = trim($titleWithSpaces); $titleText = false; # Original title text preserved for various purposes $originalTitle = $part1; # $args is a list of argument nodes, starting from index 0, not including $part1 # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object $args = null == $piece['parts'] ? array() : $piece['parts']; wfProfileOut(__METHOD__ . '-setup'); $titleProfileIn = null; // profile templates # SUBST wfProfileIn(__METHOD__ . '-modifiers'); if (!$found) { $substMatch = $this->mSubstWords->matchStartAndRemove($part1); # Possibilities for substMatch: "subst", "safesubst" or FALSE # Decide whether to expand template or keep wikitext as-is. if ($this->ot['wiki']) { if ($substMatch === false) { $literal = true; # literal when in PST with no prefix } else { $literal = false; # expand when in PST with subst: or safesubst: } } else { if ($substMatch == 'subst') { $literal = true; # literal when not in PST with plain subst: } else { $literal = false; # expand when not in PST with safesubst: or no prefix } } if ($literal) { $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args); $isLocalObj = true; $found = true; } } # Variables if (!$found && $args->getLength() == 0) { $id = $this->mVariables->matchStartToEnd($part1); if ($id !== false) { $text = $this->getVariableValue($id, $frame); if (MagicWord::getCacheTTL($id) > -1) { $this->mOutput->updateCacheExpiry(MagicWord::getCacheTTL($id)); } $found = true; } } # MSG, MSGNW and RAW if (!$found) { # Check for MSGNW: $mwMsgnw = MagicWord::get('msgnw'); if ($mwMsgnw->matchStartAndRemove($part1)) { $nowiki = true; } else { # Remove obsolete MSG: $mwMsg = MagicWord::get('msg'); $mwMsg->matchStartAndRemove($part1); } # Check for RAW: $mwRaw = MagicWord::get('raw'); if ($mwRaw->matchStartAndRemove($part1)) { $forceRawInterwiki = true; } } wfProfileOut(__METHOD__ . '-modifiers'); # Parser functions if (!$found) { wfProfileIn(__METHOD__ . '-pfunc'); $colonPos = strpos($part1, ':'); if ($colonPos !== false) { $func = substr($part1, 0, $colonPos); $funcArgs = array(trim(substr($part1, $colonPos + 1))); for ($i = 0; $i < $args->getLength(); $i++) { $funcArgs[] = $args->item($i); } try { $result = $this->callParserFunction($frame, $func, $funcArgs); } catch (Exception $ex) { wfProfileOut(__METHOD__ . '-pfunc'); wfProfileOut(__METHOD__); throw $ex; } # The interface for parser functions allows for extracting # flags into the local scope. Extract any forwarded flags # here. extract($result); } wfProfileOut(__METHOD__ . '-pfunc'); } # Finish mangling title and then check for loops. # Set $title to a Title object and $titleText to the PDBK if (!$found) { $ns = NS_TEMPLATE; # Split the title into page and subpage $subpage = ''; $relative = $this->maybeDoSubpageLink($part1, $subpage); if ($part1 !== $relative) { $part1 = $relative; $ns = $this->mTitle->getNamespace(); } $title = Title::newFromText($part1, $ns); if ($title) { $titleText = $title->getPrefixedText(); # Check for language variants if the template is not found if ($this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0) { $this->getConverterLanguage()->findVariantLink($part1, $title, true); } # Do recursion depth check $limit = $this->mOptions->getMaxTemplateDepth(); if ($frame->depth >= $limit) { $found = true; $text = '<span class="error">' . wfMessage('parser-template-recursion-depth-warning')->numParams($limit)->inContentLanguage()->text() . '</span>'; } } } # Load from database if (!$found && $title) { if (!Profiler::instance()->isPersistent()) { # Too many unique items can kill profiling DBs/collectors $titleProfileIn = __METHOD__ . "-title-" . $title->getPrefixedDBkey(); wfProfileIn($titleProfileIn); // template in } wfProfileIn(__METHOD__ . '-loadtpl'); if (!$title->isExternal()) { if ($title->isSpecialPage() && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html']) { // Pass the template arguments as URL parameters. // "uselang" will have no effect since the Language object // is forced to the one defined in ParserOptions. $pageArgs = array(); for ($i = 0; $i < $args->getLength(); $i++) { $bits = $args->item($i)->splitArg(); if (strval($bits['index']) === '') { $name = trim($frame->expand($bits['name'], PPFrame::STRIP_COMMENTS)); $value = trim($frame->expand($bits['value'])); $pageArgs[$name] = $value; } } // Create a new context to execute the special page $context = new RequestContext(); $context->setTitle($title); $context->setRequest(new FauxRequest($pageArgs)); $context->setUser($this->getUser()); $context->setLanguage($this->mOptions->getUserLangObj()); $ret = SpecialPageFactory::capturePath($title, $context); if ($ret) { $text = $context->getOutput()->getHTML(); $this->mOutput->addOutputPageMetadata($context->getOutput()); $found = true; $isHTML = true; $this->disableCache(); } } elseif (MWNamespace::isNonincludable($title->getNamespace())) { $found = false; # access denied wfDebug(__METHOD__ . ": template inclusion denied for " . $title->getPrefixedDBkey() . "\n"); } else { list($text, $title) = $this->getTemplateDom($title); if ($text !== false) { $found = true; $isChildObj = true; } } # If the title is valid but undisplayable, make a link to it if (!$found && ($this->ot['html'] || $this->ot['pre'])) { $text = "[[:{$titleText}]]"; $found = true; } } elseif ($title->isTrans()) { # Interwiki transclusion if ($this->ot['html'] && !$forceRawInterwiki) { $text = $this->interwikiTransclude($title, 'render'); $isHTML = true; } else { $text = $this->interwikiTransclude($title, 'raw'); # Preprocess it like a template $text = $this->preprocessToDom($text, self::PTD_FOR_INCLUSION); $isChildObj = true; } $found = true; } # Do infinite loop check # This has to be done after redirect resolution to avoid infinite loops via redirects if (!$frame->loopCheck($title)) { $found = true; $text = '<span class="error">' . wfMessage('parser-template-loop-warning', $titleText)->inContentLanguage()->text() . '</span>'; wfDebug(__METHOD__ . ": template loop broken at '{$titleText}'\n"); } wfProfileOut(__METHOD__ . '-loadtpl'); } # If we haven't found text to substitute by now, we're done # Recover the source wikitext and return it if (!$found) { $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args); if ($titleProfileIn) { wfProfileOut($titleProfileIn); // template out } wfProfileOut(__METHOD__); return array('object' => $text); } # Expand DOM-style return values in a child frame if ($isChildObj) { # Clean up argument array $newFrame = $frame->newChild($args, $title); if ($nowiki) { $text = $newFrame->expand($text, PPFrame::RECOVER_ORIG); } elseif ($titleText !== false && $newFrame->isEmpty()) { # Expansion is eligible for the empty-frame cache if (isset($this->mTplExpandCache[$titleText])) { $text = $this->mTplExpandCache[$titleText]; } else { $text = $newFrame->expand($text); $this->mTplExpandCache[$titleText] = $text; } } else { # Uncached expansion $text = $newFrame->expand($text); } } if ($isLocalObj && $nowiki) { $text = $frame->expand($text, PPFrame::RECOVER_ORIG); $isLocalObj = false; } if ($titleProfileIn) { wfProfileOut($titleProfileIn); // template out } # Replace raw HTML by a placeholder if ($isHTML) { $text = $this->insertStripItem($text); } elseif ($nowiki && ($this->ot['html'] || $this->ot['pre'])) { # Escape nowiki-style return values $text = wfEscapeWikiText($text); } elseif (is_string($text) && !$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\\*)/', $text)) { # Bug 529: if the template begins with a table or block-level # element, it should be treated as beginning a new line. # This behavior is somewhat controversial. $text = "\n" . $text; } if (is_string($text) && !$this->incrementIncludeSize('post-expand', strlen($text))) { # Error, oversize inclusion if ($titleText !== false) { # Make a working, properly escaped link if possible (bug 23588) $text = "[[:{$titleText}]]"; } else { # This will probably not be a working link, but at least it may # provide some hint of where the problem is preg_replace('/^:/', '', $originalTitle); $text = "[[:{$originalTitle}]]"; } $text .= $this->insertStripItem('<!-- WARNING: template omitted, post-expand include size too large -->'); $this->limitationWarn('post-expand-template-inclusion'); } if ($isLocalObj) { $ret = array('object' => $text); } else { $ret = array('text' => $text); } wfProfileOut(__METHOD__); return $ret; }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); if (is_null($params['text']) && is_null($params['appendtext']) && is_null($params['prependtext']) && $params['undo'] == 0) { $this->dieUsageMsg('missingtext'); } $pageObj = $this->getTitleOrPageId($params); $titleObj = $pageObj->getTitle(); $apiResult = $this->getResult(); if ($params['redirect']) { if ($params['prependtext'] === null && $params['appendtext'] === null && $params['section'] !== 'new') { $this->dieUsage('You have attempted to edit using the "redirect"-following' . ' mode, which must be used in conjuction with section=new, prependtext' . ', or appendtext.', 'redirect-appendonly'); } if ($titleObj->isRedirect()) { $oldTitle = $titleObj; $titles = Revision::newFromTitle($oldTitle, false, Revision::READ_LATEST)->getContent(Revision::FOR_THIS_USER, $user)->getRedirectChain(); // array_shift( $titles ); $redirValues = array(); /** @var $newTitle Title */ foreach ($titles as $id => $newTitle) { if (!isset($titles[$id - 1])) { $titles[$id - 1] = $oldTitle; } $redirValues[] = array('from' => $titles[$id - 1]->getPrefixedText(), 'to' => $newTitle->getPrefixedText()); $titleObj = $newTitle; } ApiResult::setIndexedTagName($redirValues, 'r'); $apiResult->addValue(null, 'redirects', $redirValues); // Since the page changed, update $pageObj $pageObj = WikiPage::factory($titleObj); } } if (!isset($params['contentmodel']) || $params['contentmodel'] == '') { $contentHandler = $pageObj->getContentHandler(); } else { $contentHandler = ContentHandler::getForModelID($params['contentmodel']); } $name = $titleObj->getPrefixedDBkey(); $model = $contentHandler->getModelID(); if ($contentHandler->supportsDirectApiEditing() === false) { $this->dieUsage("Direct editing via API is not supported for content model {$model} used by {$name}", 'no-direct-editing'); } if (!isset($params['contentformat']) || $params['contentformat'] == '') { $params['contentformat'] = $contentHandler->getDefaultFormat(); } $contentFormat = $params['contentformat']; if (!$contentHandler->isSupportedFormat($contentFormat)) { $this->dieUsage("The requested format {$contentFormat} is not supported for content model " . " {$model} used by {$name}", 'badformat'); } if ($params['createonly'] && $titleObj->exists()) { $this->dieUsageMsg('createonly-exists'); } if ($params['nocreate'] && !$titleObj->exists()) { $this->dieUsageMsg('nocreate-missing'); } // Now let's check whether we're even allowed to do this $errors = $titleObj->getUserPermissionsErrors('edit', $user); if (!$titleObj->exists()) { $errors = array_merge($errors, $titleObj->getUserPermissionsErrors('create', $user)); } if (count($errors)) { if (is_array($errors[0])) { switch ($errors[0][0]) { case 'blockedtext': $this->dieUsage('You have been blocked from editing', 'blocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); break; case 'autoblockedtext': $this->dieUsage('Your IP address has been blocked automatically, because it was used by a blocked user', 'autoblocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); break; default: $this->dieUsageMsg($errors[0]); } } else { $this->dieUsageMsg($errors[0]); } } $toMD5 = $params['text']; if (!is_null($params['appendtext']) || !is_null($params['prependtext'])) { $content = $pageObj->getContent(); if (!$content) { if ($titleObj->getNamespace() == NS_MEDIAWIKI) { # If this is a MediaWiki:x message, then load the messages # and return the message value for x. $text = $titleObj->getDefaultMessageText(); if ($text === false) { $text = ''; } try { $content = ContentHandler::makeContent($text, $this->getTitle()); } catch (MWContentSerializationException $ex) { $this->dieUsage($ex->getMessage(), 'parseerror'); return; } } else { # Otherwise, make a new empty content. $content = $contentHandler->makeEmptyContent(); } } // @todo Add support for appending/prepending to the Content interface if (!$content instanceof TextContent) { $mode = $contentHandler->getModelID(); $this->dieUsage("Can't append to pages using content model {$mode}", 'appendnotsupported'); } if (!is_null($params['section'])) { if (!$contentHandler->supportsSections()) { $modelName = $contentHandler->getModelID(); $this->dieUsage("Sections are not supported for this content model: {$modelName}.", 'sectionsnotsupported'); } if ($params['section'] == 'new') { // DWIM if they're trying to prepend/append to a new section. $content = null; } else { // Process the content for section edits $section = $params['section']; $content = $content->getSection($section); if (!$content) { $this->dieUsage("There is no section {$section}.", 'nosuchsection'); } } } if (!$content) { $text = ''; } else { $text = $content->serialize($contentFormat); } $params['text'] = $params['prependtext'] . $text . $params['appendtext']; $toMD5 = $params['prependtext'] . $params['appendtext']; } if ($params['undo'] > 0) { if ($params['undoafter'] > 0) { if ($params['undo'] < $params['undoafter']) { list($params['undo'], $params['undoafter']) = array($params['undoafter'], $params['undo']); } $undoafterRev = Revision::newFromId($params['undoafter']); } $undoRev = Revision::newFromId($params['undo']); if (is_null($undoRev) || $undoRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undo'])); } if ($params['undoafter'] == 0) { $undoafterRev = $undoRev->getPrevious(); } if (is_null($undoafterRev) || $undoafterRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undoafter'])); } if ($undoRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText())); } if ($undoafterRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText())); } $newContent = $contentHandler->getUndoContent($pageObj->getRevision(), $undoRev, $undoafterRev); if (!$newContent) { $this->dieUsageMsg('undo-failure'); } $params['text'] = $newContent->serialize($params['contentformat']); // If no summary was given and we only undid one rev, // use an autosummary if (is_null($params['summary']) && $titleObj->getNextRevisionID($undoafterRev->getID()) == $params['undo']) { $params['summary'] = wfMessage('undo-summary')->params($params['undo'], $undoRev->getUserText())->inContentLanguage()->text(); } } // See if the MD5 hash checks out if (!is_null($params['md5']) && md5($toMD5) !== $params['md5']) { $this->dieUsageMsg('hashcheckfailed'); } // EditPage wants to parse its stuff from a WebRequest // That interface kind of sucks, but it's workable $requestArray = array('wpTextbox1' => $params['text'], 'format' => $contentFormat, 'model' => $contentHandler->getModelID(), 'wpEditToken' => $params['token'], 'wpIgnoreBlankSummary' => true, 'wpIgnoreBlankArticle' => true, 'wpIgnoreSelfRedirect' => true, 'bot' => $params['bot']); if (!is_null($params['summary'])) { $requestArray['wpSummary'] = $params['summary']; } if (!is_null($params['sectiontitle'])) { $requestArray['wpSectionTitle'] = $params['sectiontitle']; } // TODO: Pass along information from 'undoafter' as well if ($params['undo'] > 0) { $requestArray['wpUndidRevision'] = $params['undo']; } // Watch out for basetimestamp == '' or '0' // It gets treated as NOW, almost certainly causing an edit conflict if ($params['basetimestamp'] !== null && (bool) $this->getMain()->getVal('basetimestamp')) { $requestArray['wpEdittime'] = $params['basetimestamp']; } else { $requestArray['wpEdittime'] = $pageObj->getTimestamp(); } if ($params['starttimestamp'] !== null) { $requestArray['wpStarttime'] = $params['starttimestamp']; } else { $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime } if ($params['minor'] || !$params['notminor'] && $user->getOption('minordefault')) { $requestArray['wpMinoredit'] = ''; } if ($params['recreate']) { $requestArray['wpRecreate'] = ''; } if (!is_null($params['section'])) { $section = $params['section']; if (!preg_match('/^((T-)?\\d+|new)$/', $section)) { $this->dieUsage("The section parameter must be a valid section id or 'new'", "invalidsection"); } $content = $pageObj->getContent(); if ($section !== '0' && $section != 'new' && (!$content || !$content->getSection($section))) { $this->dieUsage("There is no section {$section}.", 'nosuchsection'); } $requestArray['wpSection'] = $params['section']; } else { $requestArray['wpSection'] = ''; } $watch = $this->getWatchlistValue($params['watchlist'], $titleObj); // Deprecated parameters if ($params['watch']) { $this->logFeatureUsage('action=edit&watch'); $watch = true; } elseif ($params['unwatch']) { $this->logFeatureUsage('action=edit&unwatch'); $watch = false; } if ($watch) { $requestArray['wpWatchthis'] = ''; } // Apply change tags if (count($params['tags'])) { if ($user->isAllowed('applychangetags')) { $requestArray['wpChangeTags'] = implode(',', $params['tags']); } else { $this->dieUsage('You don\'t have permission to set change tags.', 'taggingnotallowed'); } } // Pass through anything else we might have been given, to support extensions // This is kind of a hack but it's the best we can do to make extensions work $requestArray += $this->getRequest()->getValues(); global $wgTitle, $wgRequest; $req = new DerivativeRequest($this->getRequest(), $requestArray, true); // Some functions depend on $wgTitle == $ep->mTitle // TODO: Make them not or check if they still do $wgTitle = $titleObj; $articleContext = new RequestContext(); $articleContext->setRequest($req); $articleContext->setWikiPage($pageObj); $articleContext->setUser($this->getUser()); /** @var $articleObject Article */ $articleObject = Article::newFromWikiPage($pageObj, $articleContext); $ep = new EditPage($articleObject); $ep->setApiEditOverride(true); $ep->setContextTitle($titleObj); $ep->importFormData($req); $content = $ep->textbox1; // The following is needed to give the hook the full content of the // new revision rather than just the current section. (Bug 52077) if (!is_null($params['section']) && $contentHandler->supportsSections() && $titleObj->exists()) { // If sectiontitle is set, use it, otherwise use the summary as the section title (for // backwards compatibility with old forms/bots). if ($ep->sectiontitle !== '') { $sectionTitle = $ep->sectiontitle; } else { $sectionTitle = $ep->summary; } $contentObj = $contentHandler->unserializeContent($content, $contentFormat); $fullContentObj = $articleObject->replaceSectionContent($params['section'], $contentObj, $sectionTitle); if ($fullContentObj) { $content = $fullContentObj->serialize($contentFormat); } else { // This most likely means we have an edit conflict which means that the edit // wont succeed anyway. $this->dieUsageMsg('editconflict'); } } // Run hooks // Handle APIEditBeforeSave parameters $r = array(); if (!Hooks::run('APIEditBeforeSave', array($ep, $content, &$r))) { if (count($r)) { $r['result'] = 'Failure'; $apiResult->addValue(null, $this->getModuleName(), $r); return; } $this->dieUsageMsg('hookaborted'); } // Do the actual save $oldRevId = $articleObject->getRevIdFetched(); $result = null; // Fake $wgRequest for some hooks inside EditPage // @todo FIXME: This interface SUCKS $oldRequest = $wgRequest; $wgRequest = $req; $status = $ep->attemptSave($result); $wgRequest = $oldRequest; switch ($status->value) { case EditPage::AS_HOOK_ERROR: case EditPage::AS_HOOK_ERROR_EXPECTED: if (isset($status->apiHookResult)) { $r = $status->apiHookResult; $r['result'] = 'Failure'; $apiResult->addValue(null, $this->getModuleName(), $r); return; } else { $this->dieUsageMsg('hookaborted'); } case EditPage::AS_PARSE_ERROR: $this->dieUsage($status->getMessage(), 'parseerror'); case EditPage::AS_IMAGE_REDIRECT_ANON: $this->dieUsageMsg('noimageredirect-anon'); case EditPage::AS_IMAGE_REDIRECT_LOGGED: $this->dieUsageMsg('noimageredirect-logged'); case EditPage::AS_SPAM_ERROR: $this->dieUsageMsg(array('spamdetected', $result['spam'])); case EditPage::AS_BLOCKED_PAGE_FOR_USER: $this->dieUsage('You have been blocked from editing', 'blocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED: case EditPage::AS_CONTENT_TOO_BIG: $this->dieUsageMsg(array('contenttoobig', $this->getConfig()->get('MaxArticleSize'))); case EditPage::AS_READ_ONLY_PAGE_ANON: $this->dieUsageMsg('noedit-anon'); case EditPage::AS_READ_ONLY_PAGE_LOGGED: $this->dieUsageMsg('noedit'); case EditPage::AS_READ_ONLY_PAGE: $this->dieReadOnly(); case EditPage::AS_RATE_LIMITED: $this->dieUsageMsg('actionthrottledtext'); case EditPage::AS_ARTICLE_WAS_DELETED: $this->dieUsageMsg('wasdeleted'); case EditPage::AS_NO_CREATE_PERMISSION: $this->dieUsageMsg('nocreate-loggedin'); case EditPage::AS_NO_CHANGE_CONTENT_MODEL: $this->dieUsageMsg('cantchangecontentmodel'); case EditPage::AS_BLANK_ARTICLE: $this->dieUsageMsg('blankpage'); case EditPage::AS_CONFLICT_DETECTED: $this->dieUsageMsg('editconflict'); case EditPage::AS_TEXTBOX_EMPTY: $this->dieUsageMsg('emptynewsection'); case EditPage::AS_CHANGE_TAG_ERROR: $this->dieStatus($status); case EditPage::AS_SUCCESS_NEW_ARTICLE: $r['new'] = true; // fall-through // fall-through case EditPage::AS_SUCCESS_UPDATE: $r['result'] = 'Success'; $r['pageid'] = intval($titleObj->getArticleID()); $r['title'] = $titleObj->getPrefixedText(); $r['contentmodel'] = $articleObject->getContentModel(); $newRevId = $articleObject->getLatest(); if ($newRevId == $oldRevId) { $r['nochange'] = true; } else { $r['oldrevid'] = intval($oldRevId); $r['newrevid'] = intval($newRevId); $r['newtimestamp'] = wfTimestamp(TS_ISO_8601, $pageObj->getTimestamp()); } break; case EditPage::AS_SUMMARY_NEEDED: // Shouldn't happen since we set wpIgnoreBlankSummary, but just in case $this->dieUsageMsg('summaryrequired'); case EditPage::AS_END: default: // $status came from WikiPage::doEdit() $errors = $status->getErrorsArray(); $this->dieUsageMsg($errors[0]); // TODO: Add new errors to message map break; } $apiResult->addValue(null, $this->getModuleName(), $r); }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); if (is_null($params['text']) && is_null($params['appendtext']) && is_null($params['prependtext']) && $params['undo'] == 0) { $this->dieUsageMsg('missingtext'); } $pageObj = $this->getTitleOrPageId($params); $titleObj = $pageObj->getTitle(); $apiResult = $this->getResult(); if ($params['redirect']) { if ($titleObj->isRedirect()) { $oldTitle = $titleObj; $titles = Revision::newFromTitle($oldTitle, false, Revision::READ_LATEST)->getContent(Revision::FOR_THIS_USER, $user)->getRedirectChain(); // array_shift( $titles ); $redirValues = array(); /** @var $newTitle Title */ foreach ($titles as $id => $newTitle) { if (!isset($titles[$id - 1])) { $titles[$id - 1] = $oldTitle; } $redirValues[] = array('from' => $titles[$id - 1]->getPrefixedText(), 'to' => $newTitle->getPrefixedText()); $titleObj = $newTitle; } $apiResult->setIndexedTagName($redirValues, 'r'); $apiResult->addValue(null, 'redirects', $redirValues); // Since the page changed, update $pageObj $pageObj = WikiPage::factory($titleObj); } } if (!isset($params['contentmodel']) || $params['contentmodel'] == '') { $contentHandler = $pageObj->getContentHandler(); } else { $contentHandler = ContentHandler::getForModelID($params['contentmodel']); } // @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such if (!isset($params['contentformat']) || $params['contentformat'] == '') { $params['contentformat'] = $contentHandler->getDefaultFormat(); } $contentFormat = $params['contentformat']; if (!$contentHandler->isSupportedFormat($contentFormat)) { $name = $titleObj->getPrefixedDBkey(); $model = $contentHandler->getModelID(); $this->dieUsage("The requested format {$contentFormat} is not supported for content model " . " {$model} used by {$name}", 'badformat'); } if ($params['createonly'] && $titleObj->exists()) { $this->dieUsageMsg('createonly-exists'); } if ($params['nocreate'] && !$titleObj->exists()) { $this->dieUsageMsg('nocreate-missing'); } // Now let's check whether we're even allowed to do this $errors = $titleObj->getUserPermissionsErrors('edit', $user); if (!$titleObj->exists()) { $errors = array_merge($errors, $titleObj->getUserPermissionsErrors('create', $user)); } if (count($errors)) { $this->dieUsageMsg($errors[0]); } $toMD5 = $params['text']; if (!is_null($params['appendtext']) || !is_null($params['prependtext'])) { $content = $pageObj->getContent(); if (!$content) { if ($titleObj->getNamespace() == NS_MEDIAWIKI) { # If this is a MediaWiki:x message, then load the messages # and return the message value for x. $text = $titleObj->getDefaultMessageText(); if ($text === false) { $text = ''; } try { $content = ContentHandler::makeContent($text, $this->getTitle()); } catch (MWContentSerializationException $ex) { $this->dieUsage($ex->getMessage(), 'parseerror'); return; } } else { # Otherwise, make a new empty content. $content = $contentHandler->makeEmptyContent(); } } // @todo Add support for appending/prepending to the Content interface if (!$content instanceof TextContent) { $mode = $contentHandler->getModelID(); $this->dieUsage("Can't append to pages using content model {$mode}", 'appendnotsupported'); } if (!is_null($params['section'])) { if (!$contentHandler->supportsSections()) { $modelName = $contentHandler->getModelID(); $this->dieUsage("Sections are not supported for this content model: {$modelName}.", 'sectionsnotsupported'); } // Process the content for section edits $section = intval($params['section']); $content = $content->getSection($section); if (!$content) { $this->dieUsage("There is no section {$section}.", 'nosuchsection'); } } if (!$content) { $text = ''; } else { $text = $content->serialize($contentFormat); } $params['text'] = $params['prependtext'] . $text . $params['appendtext']; $toMD5 = $params['prependtext'] . $params['appendtext']; } if ($params['undo'] > 0) { if ($params['undoafter'] > 0) { if ($params['undo'] < $params['undoafter']) { list($params['undo'], $params['undoafter']) = array($params['undoafter'], $params['undo']); } $undoafterRev = Revision::newFromID($params['undoafter']); } $undoRev = Revision::newFromID($params['undo']); if (is_null($undoRev) || $undoRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undo'])); } if ($params['undoafter'] == 0) { $undoafterRev = $undoRev->getPrevious(); } if (is_null($undoafterRev) || $undoafterRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undoafter'])); } if ($undoRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText())); } if ($undoafterRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText())); } $newContent = $contentHandler->getUndoContent($pageObj->getRevision(), $undoRev, $undoafterRev); if (!$newContent) { $this->dieUsageMsg('undo-failure'); } $params['text'] = $newContent->serialize($params['contentformat']); // If no summary was given and we only undid one rev, // use an autosummary if (is_null($params['summary']) && $titleObj->getNextRevisionID($undoafterRev->getID()) == $params['undo']) { $params['summary'] = wfMessage('undo-summary', $params['undo'], $undoRev->getUserText())->inContentLanguage()->text(); } } // See if the MD5 hash checks out if (!is_null($params['md5']) && md5($toMD5) !== $params['md5']) { $this->dieUsageMsg('hashcheckfailed'); } // EditPage wants to parse its stuff from a WebRequest // That interface kind of sucks, but it's workable $requestArray = array('wpTextbox1' => $params['text'], 'format' => $contentFormat, 'model' => $contentHandler->getModelID(), 'wpEditToken' => $params['token'], 'wpIgnoreBlankSummary' => ''); if (!is_null($params['summary'])) { $requestArray['wpSummary'] = $params['summary']; } if (!is_null($params['sectiontitle'])) { $requestArray['wpSectionTitle'] = $params['sectiontitle']; } // TODO: Pass along information from 'undoafter' as well if ($params['undo'] > 0) { $requestArray['wpUndidRevision'] = $params['undo']; } // Watch out for basetimestamp == '' // wfTimestamp() treats it as NOW, almost certainly causing an edit conflict if (!is_null($params['basetimestamp']) && $params['basetimestamp'] != '') { $requestArray['wpEdittime'] = wfTimestamp(TS_MW, $params['basetimestamp']); } else { $requestArray['wpEdittime'] = $pageObj->getTimestamp(); } if (!is_null($params['starttimestamp']) && $params['starttimestamp'] != '') { $requestArray['wpStarttime'] = wfTimestamp(TS_MW, $params['starttimestamp']); } else { $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime } if ($params['minor'] || !$params['notminor'] && $user->getOption('minordefault')) { $requestArray['wpMinoredit'] = ''; } if ($params['recreate']) { $requestArray['wpRecreate'] = ''; } if (!is_null($params['section'])) { $section = intval($params['section']); if ($section == 0 && $params['section'] != '0' && $params['section'] != 'new') { $this->dieUsage("The section parameter must be set to an integer or 'new'", "invalidsection"); } $requestArray['wpSection'] = $params['section']; } else { $requestArray['wpSection'] = ''; } $watch = $this->getWatchlistValue($params['watchlist'], $titleObj); // Deprecated parameters if ($params['watch']) { $watch = true; } elseif ($params['unwatch']) { $watch = false; } if ($watch) { $requestArray['wpWatchthis'] = ''; } global $wgTitle, $wgRequest; $req = new DerivativeRequest($this->getRequest(), $requestArray, true); // Some functions depend on $wgTitle == $ep->mTitle // TODO: Make them not or check if they still do $wgTitle = $titleObj; $articleContext = new RequestContext(); $articleContext->setRequest($req); $articleContext->setWikiPage($pageObj); $articleContext->setUser($this->getUser()); /** @var $articleObject Article */ $articleObject = Article::newFromWikiPage($pageObj, $articleContext); $ep = new EditPage($articleObject); // allow editing of non-textual content. $ep->allowNonTextContent = true; $ep->setContextTitle($titleObj); $ep->importFormData($req); // Run hooks // Handle APIEditBeforeSave parameters $r = array(); if (!wfRunHooks('APIEditBeforeSave', array($ep, $ep->textbox1, &$r))) { if (count($r)) { $r['result'] = 'Failure'; $apiResult->addValue(null, $this->getModuleName(), $r); return; } else { $this->dieUsageMsg('hookaborted'); } } // Do the actual save $oldRevId = $articleObject->getRevIdFetched(); $result = null; // Fake $wgRequest for some hooks inside EditPage // @todo FIXME: This interface SUCKS $oldRequest = $wgRequest; $wgRequest = $req; $status = $ep->internalAttemptSave($result, $user->isAllowed('bot') && $params['bot']); $wgRequest = $oldRequest; global $wgMaxArticleSize; switch ($status->value) { case EditPage::AS_HOOK_ERROR: case EditPage::AS_HOOK_ERROR_EXPECTED: $this->dieUsageMsg('hookaborted'); case EditPage::AS_PARSE_ERROR: $this->dieUsage($status->getMessage(), 'parseerror'); case EditPage::AS_IMAGE_REDIRECT_ANON: $this->dieUsageMsg('noimageredirect-anon'); case EditPage::AS_IMAGE_REDIRECT_LOGGED: $this->dieUsageMsg('noimageredirect-logged'); case EditPage::AS_SPAM_ERROR: $this->dieUsageMsg(array('spamdetected', $result['spam'])); case EditPage::AS_BLOCKED_PAGE_FOR_USER: $this->dieUsageMsg('blockedtext'); case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED: case EditPage::AS_CONTENT_TOO_BIG: $this->dieUsageMsg(array('contenttoobig', $wgMaxArticleSize)); case EditPage::AS_READ_ONLY_PAGE_ANON: $this->dieUsageMsg('noedit-anon'); case EditPage::AS_READ_ONLY_PAGE_LOGGED: $this->dieUsageMsg('noedit'); case EditPage::AS_READ_ONLY_PAGE: $this->dieReadOnly(); case EditPage::AS_RATE_LIMITED: $this->dieUsageMsg('actionthrottledtext'); case EditPage::AS_ARTICLE_WAS_DELETED: $this->dieUsageMsg('wasdeleted'); case EditPage::AS_NO_CREATE_PERMISSION: $this->dieUsageMsg('nocreate-loggedin'); case EditPage::AS_BLANK_ARTICLE: $this->dieUsageMsg('blankpage'); case EditPage::AS_CONFLICT_DETECTED: $this->dieUsageMsg('editconflict'); // case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary // case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary case EditPage::AS_TEXTBOX_EMPTY: $this->dieUsageMsg('emptynewsection'); case EditPage::AS_SUCCESS_NEW_ARTICLE: $r['new'] = ''; // fall-through // fall-through case EditPage::AS_SUCCESS_UPDATE: $r['result'] = 'Success'; $r['pageid'] = intval($titleObj->getArticleID()); $r['title'] = $titleObj->getPrefixedText(); $r['contentmodel'] = $titleObj->getContentModel(); $newRevId = $articleObject->getLatest(); if ($newRevId == $oldRevId) { $r['nochange'] = ''; } else { $r['oldrevid'] = intval($oldRevId); $r['newrevid'] = intval($newRevId); $r['newtimestamp'] = wfTimestamp(TS_ISO_8601, $pageObj->getTimestamp()); } break; case EditPage::AS_SUMMARY_NEEDED: $this->dieUsageMsg('summaryrequired'); case EditPage::AS_END: default: // $status came from WikiPage::doEdit() $errors = $status->getErrorsArray(); $this->dieUsageMsg($errors[0]); // TODO: Add new errors to message map break; } $apiResult->addValue(null, $this->getModuleName(), $r); }
/** * Create a new RequestContext object to use e.g. for calls to other parts * the software. * The object will have the WebRequest and the User object set to the ones * used in this instance. * * @return RequestContext */ public function createContext() { global $wgUser; $context = new RequestContext(); $context->setRequest($this->getMain()->getRequest()); $context->setUser($wgUser); /// @todo FIXME: we should store the User object return $context; }