protected function storeInSerializationCache($title, $oldid, $html, $etag) { global $wgMemc; // Convert the VE HTML to wikitext $text = $this->postHTML($title, $html, array('oldid' => $oldid), $etag); if ($text === false) { return false; } // Store the corresponding wikitext, referenceable by a new key $hash = md5($text); $key = wfMemcKey('visualeditor', 'serialization', $hash); $wgMemc->set($key, $text, $this->veConfig->get('VisualEditorSerializationCacheTimeout')); // Also parse and prepare the edit in case it might be saved later $page = WikiPage::factory($title); $content = ContentHandler::makeContent($text, $title, CONTENT_MODEL_WIKITEXT); $res = ApiStashEdit::parseAndStash($page, $content, $this->getUser()); if ($res === ApiStashEdit::ERROR_NONE) { wfDebugLog('StashEdit', "Cached parser output for VE content key '{$key}'."); } return $hash; }
/** * Prepare content which is about to be saved. * Returns a stdClass with source, pst and output members * * @param Content $content * @param Revision|int|null $revision Revision object. For backwards compatibility, a * revision ID is also accepted, but this is deprecated. * @param User|null $user * @param string|null $serialFormat * @param bool $useCache Check shared prepared edit cache * * @return object * * @since 1.21 */ public function prepareContentForEdit(Content $content, $revision = null, User $user = null, $serialFormat = null, $useCache = true) { global $wgContLang, $wgUser, $wgAjaxEditStash; if (is_object($revision)) { $revid = $revision->getId(); } else { $revid = $revision; // This code path is deprecated, and nothing is known to // use it, so performance here shouldn't be a worry. if ($revid !== null) { $revision = Revision::newFromId($revid, Revision::READ_LATEST); } else { $revision = null; } } $user = is_null($user) ? $wgUser : $user; // XXX: check $user->getId() here??? // Use a sane default for $serialFormat, see bug 57026 if ($serialFormat === null) { $serialFormat = $content->getContentHandler()->getDefaultFormat(); } if ($this->mPreparedEdit && isset($this->mPreparedEdit->newContent) && $this->mPreparedEdit->newContent->equals($content) && $this->mPreparedEdit->revid == $revid && $this->mPreparedEdit->format == $serialFormat) { // Already prepared return $this->mPreparedEdit; } // The edit may have already been prepared via api.php?action=stashedit $cachedEdit = $useCache && $wgAjaxEditStash ? ApiStashEdit::checkCache($this->getTitle(), $content, $user) : false; $popts = ParserOptions::newFromUserAndLang($user, $wgContLang); Hooks::run('ArticlePrepareTextForEdit', [$this, $popts]); $edit = (object) []; if ($cachedEdit) { $edit->timestamp = $cachedEdit->timestamp; } else { $edit->timestamp = wfTimestampNow(); } // @note: $cachedEdit is safely not used if the rev ID was referenced in the text $edit->revid = $revid; if ($cachedEdit) { $edit->pstContent = $cachedEdit->pstContent; } else { $edit->pstContent = $content ? $content->preSaveTransform($this->mTitle, $user, $popts) : null; } $edit->format = $serialFormat; $edit->popts = $this->makeParserOptions('canonical'); if ($cachedEdit) { $edit->output = $cachedEdit->output; } else { if ($revision) { // We get here if vary-revision is set. This means that this page references // itself (such as via self-transclusion). In this case, we need to make sure // that any such self-references refer to the newly-saved revision, and not // to the previous one, which could otherwise happen due to replica DB lag. $oldCallback = $edit->popts->getCurrentRevisionCallback(); $edit->popts->setCurrentRevisionCallback(function (Title $title, $parser = false) use($revision, &$oldCallback) { if ($title->equals($revision->getTitle())) { return $revision; } else { return call_user_func($oldCallback, $title, $parser); } }); } else { // Try to avoid a second parse if {{REVISIONID}} is used $edit->popts->setSpeculativeRevIdCallback(function () { return 1 + (int) wfGetDB(DB_MASTER)->selectField('revision', 'MAX(rev_id)', [], __METHOD__); }); } $edit->output = $edit->pstContent ? $edit->pstContent->getParserOutput($this->mTitle, $revid, $edit->popts) : null; } $edit->newContent = $content; $edit->oldContent = $this->getContent(Revision::RAW); // NOTE: B/C for hooks! don't use these fields! $edit->newText = $edit->newContent ? ContentHandler::getContentText($edit->newContent) : ''; $edit->oldText = $edit->oldContent ? ContentHandler::getContentText($edit->oldContent) : ''; $edit->pst = $edit->pstContent ? $edit->pstContent->serialize($serialFormat) : ''; if ($edit->output) { $edit->output->setCacheTime(wfTimestampNow()); } // Process cache the result $this->mPreparedEdit = $edit; return $edit; }
/** * Get the rendered text for previewing. * @throws MWException * @return string */ function getPreviewText() { global $wgOut, $wgUser, $wgRawHtml, $wgLang; global $wgAllowUserCss, $wgAllowUserJs; $stats = $wgOut->getContext()->getStats(); if ($wgRawHtml && !$this->mTokenOk) { // Could be an offsite preview attempt. This is very unsafe if // HTML is enabled, as it could be an attack. $parsedNote = ''; if ($this->textbox1 !== '') { // Do not put big scary notice, if previewing the empty // string, which happens when you initially edit // a category page, due to automatic preview-on-open. $parsedNote = $wgOut->parse("<div class='previewnote'>" . wfMessage('session_fail_preview_html')->text() . "</div>", true, true); } $stats->increment('edit.failures.session_loss'); return $parsedNote; } $note = ''; try { $content = $this->toEditContent($this->textbox1); $previewHTML = ''; if (!Hooks::run('AlternateEditPreview', array($this, &$content, &$previewHTML, &$this->mParserOutput))) { return $previewHTML; } # provide a anchor link to the editform $continueEditing = '<span class="mw-continue-editing">' . '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage('continue-editing')->text() . ']]</span>'; if ($this->mTriedSave && !$this->mTokenOk) { if ($this->mTokenOkExceptSuffix) { $note = wfMessage('token_suffix_mismatch')->plain(); $stats->increment('edit.failures.bad_token'); } else { $note = wfMessage('session_fail_preview')->plain(); $stats->increment('edit.failures.session_loss'); } } elseif ($this->incompleteForm) { $note = wfMessage('edit_form_incomplete')->plain(); if ($this->mTriedSave) { $stats->increment('edit.failures.incomplete_form'); } } else { $note = wfMessage('previewnote')->plain() . ' ' . $continueEditing; } $parserOptions = $this->page->makeParserOptions($this->mArticle->getContext()); $parserOptions->setIsPreview(true); $parserOptions->setIsSectionPreview(!is_null($this->section) && $this->section !== ''); # don't parse non-wikitext pages, show message about preview if ($this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage()) { if ($this->mTitle->isCssJsSubpage()) { $level = 'user'; } elseif ($this->mTitle->isCssOrJsPage()) { $level = 'site'; } else { $level = false; } if ($content->getModel() == CONTENT_MODEL_CSS) { $format = 'css'; if ($level === 'user' && !$wgAllowUserCss) { $format = false; } } elseif ($content->getModel() == CONTENT_MODEL_JAVASCRIPT) { $format = 'js'; if ($level === 'user' && !$wgAllowUserJs) { $format = false; } } else { $format = false; } # Used messages to make sure grep find them: # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview if ($level && $format) { $note = "<div id='mw-{$level}{$format}preview'>" . wfMessage("{$level}{$format}preview")->text() . ' ' . $continueEditing . "</div>"; } } # If we're adding a comment, we need to show the # summary as the headline if ($this->section === "new" && $this->summary !== "") { $content = $content->addSectionHeader($this->summary); } $hook_args = array($this, &$content); ContentHandler::runLegacyHooks('EditPageGetPreviewText', $hook_args); Hooks::run('EditPageGetPreviewContent', $hook_args); $parserOptions->enableLimitReport(); # For CSS/JS pages, we should have called the ShowRawCssJs hook here. # But it's now deprecated, so never mind $pstContent = $content->preSaveTransform($this->mTitle, $wgUser, $parserOptions); $scopedCallback = $parserOptions->setupFakeRevision($this->mTitle, $pstContent, $wgUser); $parserOutput = $pstContent->getParserOutput($this->mTitle, null, $parserOptions); # Try to stash the edit for the final submission step # @todo: different date format preferences cause cache misses ApiStashEdit::stashEditFromPreview($this->getArticle(), $content, $pstContent, $parserOutput, $parserOptions, $parserOptions, wfTimestampNow()); $parserOutput->setEditSectionTokens(false); // no section edit links $previewHTML = $parserOutput->getText(); $this->mParserOutput = $parserOutput; $wgOut->addParserOutputMetadata($parserOutput); if (count($parserOutput->getWarnings())) { $note .= "\n\n" . implode("\n\n", $parserOutput->getWarnings()); } ScopedCallback::consume($scopedCallback); } catch (MWContentSerializationException $ex) { $m = wfMessage('content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage()); $note .= "\n\n" . $m->parse(); $previewHTML = ''; } if ($this->isConflict) { $conflict = '<h2 id="mw-previewconflict">' . wfMessage('previewconflict')->escaped() . "</h2>\n"; } else { $conflict = '<hr />'; } $previewhead = "<div class='previewnote'>\n" . '<h2 id="mw-previewheader">' . wfMessage('preview')->escaped() . "</h2>" . $wgOut->parse($note, true, true) . $conflict . "</div>\n"; $pageViewLang = $this->mTitle->getPageViewLanguage(); $attribs = array('lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(), 'class' => 'mw-content-' . $pageViewLang->getDir()); $previewHTML = Html::rawElement('div', $attribs, $previewHTML); return $previewhead . $previewHTML . $this->previewTextAfterContent; }