public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); $title = Title::newFromText($params['page']); if (!$title) { $this->dieUsageMsg('invalidtitle', $params['page']); } $isSafeAction = in_array($params['paction'], self::$SAFE_ACTIONS, true); $availableNamespaces = $this->veConfig->get('VisualEditorAvailableNamespaces'); if (!$isSafeAction && (!isset($availableNamespaces[$title->getNamespace()]) || !$availableNamespaces[$title->getNamespace()])) { $this->dieUsage("VisualEditor is not enabled in namespace " . $title->getNamespace(), 'novenamespace'); } $parserParams = array(); if (isset($params['oldid'])) { $parserParams['oldid'] = $params['oldid']; } $html = $params['html']; if (substr($html, 0, 11) === 'rawdeflate,') { $deflated = base64_decode(substr($html, 11)); wfSuppressWarnings(); $html = gzinflate($deflated); wfRestoreWarnings(); if ($deflated === $html || $html === false) { $this->dieUsage("HTML provided is not properly deflated", 'invaliddeflate'); } } wfDebugLog('visualeditor', "called on '{$title}' with paction: '{$params['paction']}'"); switch ($params['paction']) { case 'parse': case 'metadata': // Dirty hack to provide the correct context for edit notices global $wgTitle; // FIXME NOOOOOOOOES $wgTitle = $title; RequestContext::getMain()->setTitle($title); // Get information about current revision if ($title->exists()) { $latestRevision = Revision::newFromTitle($title); if ($latestRevision === null) { $this->dieUsage('Could not find latest revision for title', 'latestnotfound'); } $revision = null; if (!isset($parserParams['oldid']) || $parserParams['oldid'] === 0) { $parserParams['oldid'] = $latestRevision->getId(); $revision = $latestRevision; } else { $revision = Revision::newFromId($parserParams['oldid']); if ($revision === null) { $this->dieUsage('Could not find revision ID ' . $parserParams['oldid'], 'oldidnotfound'); } } $restoring = $revision && !$revision->isCurrent(); $baseTimestamp = $latestRevision->getTimestamp(); $oldid = intval($parserParams['oldid']); // If requested, request HTML from Parsoid/RESTBase if ($params['paction'] === 'parse') { $content = $this->requestRestbase('GET', 'page/html/' . urlencode($title->getPrefixedDBkey()) . '/' . $oldid, array()); if ($content === false) { $this->dieUsage('Error contacting the document server', 'docserver'); } } } else { $content = ''; $baseTimestamp = wfTimestampNow(); $oldid = 0; $restoring = false; } // Get edit notices $notices = $title->getEditNotices(); // Anonymous user notice if ($user->isAnon()) { $notices[] = $this->msg('anoneditwarning', '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}', '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}')->parseAsBlock(); } // Old revision notice if ($restoring) { $notices[] = $this->msg('editingold')->parseAsBlock(); } // New page notices if (!$title->exists()) { $notices[] = $this->msg($user->isLoggedIn() ? 'newarticletext' : 'newarticletextanon', wfExpandUrl(Skin::makeInternalOrExternalUrl($this->msg('helppage')->inContentLanguage()->text())))->parseAsBlock(); // Page protected from creation if ($title->getRestrictions('create')) { $notices[] = $this->msg('titleprotectedwarning')->parseAsBlock(); } } // Look at protection status to set up notices + surface class(es) $protectedClasses = array(); if (MWNamespace::getRestrictionLevels($title->getNamespace()) !== array('')) { // Page protected from editing if ($title->isProtected('edit')) { # Is the title semi-protected? if ($title->isSemiProtected()) { $protectedClasses[] = 'mw-textarea-sprotected'; $noticeMsg = 'semiprotectedpagewarning'; } else { $protectedClasses[] = 'mw-textarea-protected'; # Then it must be protected based on static groups (regular) $noticeMsg = 'protectedpagewarning'; } $notices[] = $this->msg($noticeMsg)->parseAsBlock() . $this->getLastLogEntry($title, 'protect'); } // Deal with cascading edit protection list($sources, $restrictions) = $title->getCascadeProtectionSources(); if (isset($restrictions['edit'])) { $protectedClasses[] = ' mw-textarea-cprotected'; $notice = $this->msg('cascadeprotectedwarning')->parseAsBlock() . '<ul>'; // Unfortunately there's no nice way to get only the pages which cause // editing to be restricted foreach ($sources as $source) { $notice .= "<li>" . Linker::link($source) . "</li>"; } $notice .= '</ul>'; $notices[] = $notice; } } // Permission notice $permErrors = $title->getUserPermissionsErrors('create', $user); if ($permErrors && !$title->exists()) { $notices[] = $this->msg('permissionserrorstext-withaction', 1, $this->msg('action-createpage')) . "<br>" . call_user_func_array(array($this, 'msg'), $permErrors[0])->parse(); } // Show notice when editing user / user talk page of a user that doesn't exist // or who is blocked // HACK of course this code is partly duplicated from EditPage.php :( if ($title->getNamespace() == NS_USER || $title->getNamespace() == NS_USER_TALK) { $parts = explode('/', $title->getText(), 2); $targetUsername = $parts[0]; $targetUser = User::newFromName($targetUsername, false); if (!($targetUser && $targetUser->isLoggedIn()) && !User::isIP($targetUsername)) { // User does not exist $notices[] = "<div class=\"mw-userpage-userdoesnotexist error\">\n" . $this->msg('userpage-userdoesnotexist', wfEscapeWikiText($targetUsername)) . "\n</div>"; } elseif ($targetUser->isBlocked()) { // Show log extract if the user is currently blocked $notices[] = $this->msg('blocked-notice-logextract', $targetUser->getName())->parseAsBlock() . $this->getLastLogEntry($targetUser->getUserPage(), 'block'); } } // Blocked user notice if ($user->isBlockedFrom($title) && $user->getBlock()->prevents('edit') !== false) { $notices[] = call_user_func_array(array($this, 'msg'), $user->getBlock()->getPermissionsError($this->getContext()))->parseAsBlock(); } // Blocked user notice for global blocks if (class_exists('GlobalBlocking')) { $error = GlobalBlocking::getUserBlockErrors($user, $this->getRequest()->getIP()); if (count($error)) { $notices[] = call_user_func_array(array($this, 'msg'), $error)->parseAsBlock(); } } // HACK: Build a fake EditPage so we can get checkboxes from it $article = new Article($title); // Deliberately omitting ,0 so oldid comes from request $ep = new EditPage($article); $req = $this->getRequest(); $req->setVal('format', 'text/x-wiki'); $ep->importFormData($req); // By reference for some reason (bug 52466) $tabindex = 0; $states = array('minor' => false, 'watch' => false); $checkboxes = $ep->getCheckboxes($tabindex, $states); // HACK: Find out which red links are on the page // We do the lookup for the current version. This might not be entirely complete // if we're loading an oldid, but it'll probably be close enough, and LinkCache // will automatically request any additional data it needs. $links = array(); $wikipage = WikiPage::factory($title); $popts = $wikipage->makeParserOptions('canonical'); $cached = ParserCache::singleton()->get($article, $popts, true); $links = array('missing' => array(), 'known' => $restoring || !$cached ? array() : 1); if ($cached) { foreach ($cached->getLinks() as $namespace => $cachedTitles) { foreach ($cachedTitles as $cachedTitleText => $exists) { $cachedTitle = Title::makeTitle($namespace, $cachedTitleText); if (!$cachedTitle->isKnown()) { $links['missing'][] = $cachedTitle->getPrefixedText(); } elseif ($links['known'] !== 1) { $links['known'][] = $cachedTitle->getPrefixedText(); } } } } // Add information about current page if (!$title->isKnown()) { $links['missing'][] = $title->getPrefixedText(); } elseif ($links['known'] !== 1) { $links['known'][] = $title->getPrefixedText(); } // On parser cache miss, just don't bother populating red link data $result = array('result' => 'success', 'notices' => $notices, 'checkboxes' => $checkboxes, 'links' => $links, 'protectedClasses' => implode(' ', $protectedClasses), 'watched' => $user->isWatched($title), 'basetimestamp' => $baseTimestamp, 'starttimestamp' => wfTimestampNow(), 'oldid' => $oldid); if ($params['paction'] === 'parse') { $result['content'] = $content; } break; case 'parsefragment': $wikitext = $params['wikitext']; if ($params['pst']) { $wikitext = $this->pstWikitext($title, $wikitext); } $content = $this->parseWikitextFragment($title, $wikitext); if ($content === false) { $this->dieUsage('Error contacting the document server', 'docserver'); } else { $result = array('result' => 'success', 'content' => $content); } break; case 'serialize': if ($params['cachekey'] !== null) { $content = $this->trySerializationCache($params['cachekey']); if (!is_string($content)) { $this->dieUsage('No cached serialization found with that key', 'badcachekey'); } } else { if ($params['html'] === null) { $this->dieUsageMsg('missingparam', 'html'); } $content = $this->postHTML($title, $html, $parserParams, $params['etag']); if ($content === false) { $this->dieUsage('Error contacting the document server', 'docserver'); } } $result = array('result' => 'success', 'content' => $content); break; case 'diff': if ($params['cachekey'] !== null) { $wikitext = $this->trySerializationCache($params['cachekey']); if (!is_string($wikitext)) { $this->dieUsage('No cached serialization found with that key', 'badcachekey'); } } else { $wikitext = $this->postHTML($title, $html, $parserParams, $params['etag']); if ($wikitext === false) { $this->dieUsage('Error contacting the document server', 'docserver'); } } $diff = $this->diffWikitext($title, $wikitext); if ($diff['result'] === 'fail') { $this->dieUsage('Diff failed', 'difffailed'); } $result = $diff; break; case 'serializeforcache': if (!isset($parserParams['oldid'])) { $parserParams['oldid'] = Revision::newFromTitle($title)->getId(); } $key = $this->storeInSerializationCache($title, $parserParams['oldid'], $html, $params['etag']); $result = array('result' => 'success', 'cachekey' => $key); break; case 'getlanglinks': $langlinks = $this->getLangLinks($title); if ($langlinks === false) { $this->dieUsage('Error querying MediaWiki API', 'api-langlinks-error'); } else { $result = array('result' => 'success', 'langlinks' => $langlinks); } break; } $this->getResult()->addValue(null, $this->getModuleName(), $result); }
public function execute() { global $wgVisualEditorNamespaces, $wgVisualEditorParsoidURL, $wgVisualEditorParsoidTimeout, $wgDevelEnvironment; $user = $this->getUser(); $params = $this->extractRequestParams(); $page = Title::newFromText($params['page']); if (!$page) { $this->dieUsageMsg('invalidtitle', $params['page']); } if (!in_array($page->getNamespace(), $wgVisualEditorNamespaces)) { $this->dieUsage("VisualEditor is not enabled in namespace " . $page->getNamespace(), 'novenamespace'); } $parserParams = array(); if (isset($params['oldwt'])) { $parserParams['oldwt'] = $params['oldwt']; } else { if (isset($params['oldid'])) { $parserParams['oldid'] = $params['oldid']; } } switch ($params['paction']) { case 'parsewt': // FIXME: Perhaps requestParsoid method should be used here $postData = array('wt' => $params['wikitext']); $content = Http::post($wgVisualEditorParsoidURL . '/' . urlencode($this->getApiSource()) . '/' . urlencode($page->getPrefixedDBkey()), array('postData' => $postData, 'timeout' => $wgVisualEditorParsoidTimeout, 'noProxy' => !empty($wgDevelEnvironment))); $result = array('result' => 'success', 'content' => $content); break; case 'parse': $parsed = $this->getHTML($page, $parserParams); // Dirty hack to provide the correct context for edit notices global $wgTitle; // FIXME NOOOOOOOOES $wgTitle = $page; RequestContext::getMain()->setTitle($page); // TODO: In MW 1.19.7 method getEditNotices does not exist so for now fallback to just an empty // but in future figure out what's the proper backward compatibility solution. // #back-compat // $notices = $page->getEditNotices(); $notices = array(); $anoneditwarning = false; $anoneditwarningMessage = $this->msg('VisualEditor-anoneditwarning'); if ($user->isAnon() && $anoneditwarningMessage->exists()) { $notices[] = $anoneditwarningMessage->parseAsBlock(); $anoneditwarning = true; } if ($parsed && $parsed['restoring']) { $notices[] = $this->msg('editingold')->parseAsBlock(); } // Creating new page if (!$page->exists()) { $notices[] = $this->msg($user->isLoggedIn() ? 'newarticletext' : 'newarticletextanon', Skin::makeInternalOrExternalUrl($this->msg('helppage')->inContentLanguage()->text()))->parseAsBlock(); // Page protected from creation if ($page->getRestrictions('create')) { $notices[] = $this->msg('titleprotectedwarning')->parseAsBlock(); } } // Look at protection status to set up notices + surface class(es) $protectedClasses = array(); if (MWNamespace::getRestrictionLevels($page->getNamespace()) !== array('')) { // Page protected from editing if ($page->isProtected('edit')) { # Is the title semi-protected? if ($page->isSemiProtected()) { $protectedClasses[] = 'mw-textarea-sprotected'; $noticeMsg = 'semiprotectedpagewarning'; } else { $protectedClasses[] = 'mw-textarea-protected'; # Then it must be protected based on static groups (regular) $noticeMsg = 'protectedpagewarning'; } $notices[] = $this->msg($noticeMsg)->parseAsBlock(); } // Deal with cascading edit protection list($sources, $restrictions) = $page->getCascadeProtectionSources(); if (isset($restrictions['edit'])) { $protectedClasses[] = ' mw-textarea-cprotected'; $notice = $this->msg('cascadeprotectedwarning')->parseAsBlock() . '<ul>'; // Unfortunately there's no nice way to get only the pages which cause // editing to be restricted foreach ($sources as $source) { $notice .= "<li>" . Linker::link($source) . "</li>"; } $notice .= '</ul>'; $notices[] = $notice; } } // Show notice when editing user / user talk page of a user that doesn't exist // or who is blocked // HACK of course this code is partly duplicated from EditPage.php :( if ($page->getNamespace() == NS_USER || $page->getNamespace() == NS_USER_TALK) { $parts = explode('/', $page->getText(), 2); $targetUsername = $parts[0]; $targetUser = User::newFromName($targetUsername, false); if (!($targetUser && $targetUser->isLoggedIn()) && !User::isIP($targetUsername)) { // User does not exist $notices[] = "<div class=\"mw-userpage-userdoesnotexist error\">\n" . $this->msg('userpage-userdoesnotexist', wfEscapeWikiText($targetUsername)) . "\n</div>"; } // Some upstream code is deleted from here, more information: // https://github.com/Wikia/app/commit/d54b481d3f6e5b092b212a2c98b2cb5452bee26c // https://github.com/Wikia/app/commit/681e7437078206460f7c0cb1837095e656d8ba85 } if (class_exists('GlobalBlocking')) { $error = GlobalBlocking::getUserBlockErrors($user, $this->getRequest()->getIP()); if (count($error)) { $notices[] = call_user_func_array(array($this, 'msg'), $error)->parseAsBlock(); } } // HACK: Build a fake EditPage so we can get checkboxes from it $article = new Article($page); // Deliberately omitting ,0 so oldid comes from request $ep = new EditPage($article); $req = $this->getRequest(); $req->setVal('format', 'text/x-wiki'); $ep->importFormData($req); // By reference for some reason (bug 52466) $tabindex = 0; $states = array('minor' => false, 'watch' => false); $checkboxes = $ep->getCheckboxes($tabindex, $states); // HACK: Find out which red links are on the page // We do the lookup for the current version. This might not be entirely complete // if we're loading an oldid, but it'll probably be close enough, and LinkCache // will automatically request any additional data it needs. $links = array(); $wikipage = WikiPage::factory($page); $popts = $wikipage->makeParserOptions('canonical'); $cached = ParserCache::singleton()->get($article, $popts, true); if ($cached) { foreach ($cached->getLinks() as $ns => $dbks) { foreach ($dbks as $dbk => $id) { $links[Title::makeTitle($ns, $dbk)->getPrefixedText()] = array('missing' => $id == 0); } } } // On parser cache miss, just don't bother populating red link data if ($parsed === false) { $this->dieUsage('Error contacting the Parsoid server', 'parsoidserver'); } else { $result = array_merge(array('result' => 'success', 'notices' => $notices, 'checkboxes' => $checkboxes, 'links' => $links, 'protectedClasses' => implode(' ', $protectedClasses), 'anoneditwarning' => $anoneditwarning), $parsed['result']); } break; case 'parsefragment': $content = $this->parseWikitextFragment($page, $params['wikitext']); if ($content === false) { $this->dieUsage('Error contacting the Parsoid server', 'parsoidserver'); } else { $result = array('result' => 'success', 'content' => $content); } break; case 'serialize': if ($params['cachekey'] !== null) { $content = $this->trySerializationCache($params['cachekey']); if (!is_string($content)) { $this->dieUsage('No cached serialization found with that key', 'badcachekey'); } } else { if ($params['html'] === null) { $this->dieUsageMsg('missingparam', 'html'); } $html = $params['html']; $content = $this->postHTML($page, $html, $parserParams); if ($content === false) { $this->dieUsage('Error contacting the Parsoid server', 'parsoidserver'); } } $result = array('result' => 'success', 'content' => $content); break; case 'diff': if ($params['cachekey'] !== null) { $wikitext = $this->trySerializationCache($params['cachekey']); if (!is_string($wikitext)) { $this->dieUsage('No cached serialization found with that key', 'badcachekey'); } } else { $wikitext = $this->postHTML($page, $params['html'], $parserParams); if ($wikitext === false) { $this->dieUsage('Error contacting the Parsoid server', 'parsoidserver'); } } $diff = $this->diffWikitext($page, $wikitext); if ($diff['result'] === 'fail') { $this->dieUsage('Diff failed', 'difffailed'); } $result = $diff; break; case 'serializeforcache': $key = $this->storeInSerializationCache($page, $parserParams['oldid'], $params['html']); $result = array('result' => 'success', 'cachekey' => $key); break; case 'getlanglinks': $langlinks = $this->getLangLinks($page); if ($langlinks === false) { $this->dieUsage('Error querying MediaWiki API', 'parsoidserver'); } else { $result = array('result' => 'success', 'langlinks' => $langlinks); } break; } $this->getResult()->addValue(null, $this->getModuleName(), $result); }