/** * Show the new revision of the page. */ public function renderNewRevision() { $out = $this->getOutput(); $revHeader = $this->getRevisionHeader($this->mNewRev); # Add "current version as of X" title $out->addHTML("<hr class='diff-hr' id='mw-oldid' />\n\t\t<h2 class='diff-currentversion-title'>{$revHeader}</h2>\n"); # Page content may be handled by a hooked call instead... # @codingStandardsIgnoreStart Ignoring long lines. if (Hooks::run('ArticleContentOnDiff', array($this, $out))) { $this->loadNewText(); $out->setRevisionId($this->mNewid); $out->setRevisionTimestamp($this->mNewRev->getTimestamp()); $out->setArticleFlag(true); // NOTE: only needed for B/C: custom rendering of JS/CSS via hook if ($this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage()) { // This needs to be synchronised with Article::showCssOrJsPage(), which sucks // Give hooks a chance to customise the output // @todo standardize this crap into one function if (ContentHandler::runLegacyHooks('ShowRawCssJs', array($this->mNewContent, $this->mNewPage, $out))) { // NOTE: deprecated hook, B/C only // use the content object's own rendering $cnt = $this->mNewRev->getContent(); $po = $cnt ? $cnt->getParserOutput($this->mNewRev->getTitle(), $this->mNewRev->getId()) : null; if ($po) { $out->addParserOutputContent($po); } } } elseif (!Hooks::run('ArticleContentViewCustom', array($this->mNewContent, $this->mNewPage, $out))) { // Handled by extension } elseif (!ContentHandler::runLegacyHooks('ArticleViewCustom', array($this->mNewContent, $this->mNewPage, $out))) { // NOTE: deprecated hook, B/C only // Handled by extension } else { // Normal page if ($this->getTitle()->equals($this->mNewPage)) { // If the Title stored in the context is the same as the one // of the new revision, we can use its associated WikiPage // object. $wikiPage = $this->getWikiPage(); } else { // Otherwise we need to create our own WikiPage object $wikiPage = WikiPage::factory($this->mNewPage); } $parserOutput = $this->getParserOutput($wikiPage, $this->mNewRev); # WikiPage::getParserOutput() should not return false, but just in case if ($parserOutput) { $out->addParserOutput($parserOutput); } } } # @codingStandardsIgnoreEnd # Add redundant patrol link on bottom... $out->addHTML($this->markPatrolledLink()); }
/** * Change an existing article or create a new article. Updates RC and all necessary caches, * optionally via the deferred update array. * * @param $content Content: new content * @param string $summary edit summary * @param $flags Integer bitfield: * EDIT_NEW * Article is known or assumed to be non-existent, create a new one * EDIT_UPDATE * Article is known or assumed to be pre-existing, update it * EDIT_MINOR * Mark this edit minor, if the user is allowed to do so * EDIT_SUPPRESS_RC * Do not log the change in recentchanges * EDIT_FORCE_BOT * Mark the edit a "bot" edit regardless of user rights * EDIT_DEFER_UPDATES * Defer some of the updates until the end of index.php * EDIT_AUTOSUMMARY * Fill in blank summaries with generated text where possible * * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an * edit-already-exists error will be returned. These two conditions are also possible with * auto-detection due to MediaWiki's performance-optimised locking strategy. * * @param bool|int $baseRevId the revision ID this edit was based off, if any * @param $user User the user doing the edit * @param $serialisation_format String: format for storing the content in the database * * @throws MWException * @return Status object. Possible errors: * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status * edit-gone-missing: In update mode, but the article didn't exist * edit-conflict: In update mode, the article changed unexpectedly * edit-no-change: Warning that the text was the same as before * edit-already-exists: In creation mode, but the article already exists * * Extensions may define additional errors. * * $return->value will contain an associative array with members as follows: * new: Boolean indicating if the function attempted to create a new article * revision: The revision object for the inserted revision, or null * * @since 1.21 */ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false, User $user = null, $serialisation_format = null ) { global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol; // Low-level sanity check if ( $this->mTitle->getText() === '' ) { throw new MWException( 'Something is trying to edit an article with an empty title' ); } wfProfileIn( __METHOD__ ); if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) { wfProfileOut( __METHOD__ ); return Status::newFatal( 'content-not-allowed-here', ContentHandler::getLocalizedName( $content->getModel() ), $this->getTitle()->getPrefixedText() ); } $user = is_null( $user ) ? $wgUser : $user; $status = Status::newGood( array() ); // Load the data from the master database if needed. // The caller may already loaded it from the master or even loaded it using // SELECT FOR UPDATE, so do not override that using clear(). $this->loadPageData( 'fromdbmaster' ); $flags = $this->checkFlags( $flags ); // handle hook $hook_args = array( &$this, &$user, &$content, &$summary, $flags & EDIT_MINOR, null, null, &$flags, &$status ); if ( !wfRunHooks( 'PageContentSave', $hook_args ) || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) { wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" ); if ( $status->isOK() ) { $status->fatal( 'edit-hook-aborted' ); } wfProfileOut( __METHOD__ ); return $status; } // Silently ignore EDIT_MINOR if not allowed $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' ); $bot = $flags & EDIT_FORCE_BOT; $old_content = $this->getContent( Revision::RAW ); // current revision's content $oldsize = $old_content ? $old_content->getSize() : 0; $oldid = $this->getLatest(); $oldIsRedirect = $this->isRedirect(); $oldcountable = $this->isCountable(); $handler = $content->getContentHandler(); // Provide autosummaries if one is not provided and autosummaries are enabled. if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) { if ( !$old_content ) { $old_content = null; } $summary = $handler->getAutosummary( $old_content, $content, $flags ); } $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format ); $serialized = $editInfo->pst; /** * @var Content $content */ $content = $editInfo->pstContent; $newsize = $content->getSize(); $dbw = wfGetDB( DB_MASTER ); $now = wfTimestampNow(); $this->mTimestamp = $now; if ( $flags & EDIT_UPDATE ) { // Update article, but only if changed. $status->value['new'] = false; if ( !$oldid ) { // Article gone missing wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" ); $status->fatal( 'edit-gone-missing' ); wfProfileOut( __METHOD__ ); return $status; } elseif ( !$old_content ) { // Sanity check for bug 37225 wfProfileOut( __METHOD__ ); throw new MWException( "Could not find text for current revision {$oldid}." ); } $revision = new Revision( array( 'page' => $this->getId(), 'title' => $this->getTitle(), // for determining the default content model 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'parent_id' => $oldid, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialisation_format, ) ); // XXX: pass content object?! $changed = !$content->equals( $old_content ); if ( $changed ) { if ( !$content->isValid() ) { wfProfileOut( __METHOD__ ); throw new MWException( "New content failed validity check!" ); } $dbw->begin( __METHOD__ ); $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user ); $status->merge( $prepStatus ); if ( !$status->isOK() ) { $dbw->rollback( __METHOD__ ); wfProfileOut( __METHOD__ ); return $status; } $revisionId = $revision->insertOn( $dbw ); // Update page // // Note that we use $this->mLatest instead of fetching a value from the master DB // during the course of this function. This makes sure that EditPage can detect // edit conflicts reliably, either by $ok here, or by $article->getTimestamp() // before this function is called. A previous function used a separate query, this // creates a window where concurrent edits can cause an ignored edit conflict. $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect ); if ( !$ok ) { // Belated edit conflict! Run away!! $status->fatal( 'edit-conflict' ); $dbw->rollback( __METHOD__ ); wfProfileOut( __METHOD__ ); return $status; } wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) ); // Update recentchanges if ( !( $flags & EDIT_SUPPRESS_RC ) ) { // Mark as patrolled if the user can do so $patrolled = $wgUseRCPatrol && !count( $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); // Add RC row to the DB $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary, $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize, $revisionId, $patrolled ); // Log auto-patrolled edits if ( $patrolled ) { PatrolLog::record( $rc, true, $user ); } } $user->incEditCount(); $dbw->commit( __METHOD__ ); } else { // Bug 32948: revision ID must be set to page {{REVISIONID}} and // related variables correctly $revision->setId( $this->getLatest() ); } // Update links tables, site stats, etc. $this->doEditUpdates( $revision, $user, array( 'changed' => $changed, 'oldcountable' => $oldcountable ) ); if ( !$changed ) { $status->warning( 'edit-no-change' ); $revision = null; // Update page_touched, this is usually implicit in the page update // Other cache updates are done in onArticleEdit() $this->mTitle->invalidateCache(); } } else { // Create new article $status->value['new'] = true; $dbw->begin( __METHOD__ ); $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user ); $status->merge( $prepStatus ); if ( !$status->isOK() ) { $dbw->rollback( __METHOD__ ); wfProfileOut( __METHOD__ ); return $status; } $status->merge( $prepStatus ); // Add the page record; stake our claim on this title! // This will return false if the article already exists $newid = $this->insertOn( $dbw ); if ( $newid === false ) { $dbw->rollback( __METHOD__ ); $status->fatal( 'edit-already-exists' ); wfProfileOut( __METHOD__ ); return $status; } // Save the revision text... $revision = new Revision( array( 'page' => $newid, 'title' => $this->getTitle(), // for determining the default content model 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialisation_format, ) ); $revisionId = $revision->insertOn( $dbw ); // Bug 37225: use accessor to get the text as Revision may trim it $content = $revision->getContent(); // sanity; get normalized version if ( $content ) { $newsize = $content->getSize(); } // Update the page record with revision data $this->updateRevisionOn( $dbw, $revision, 0 ); wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) ); // Update recentchanges if ( !( $flags & EDIT_SUPPRESS_RC ) ) { // Mark as patrolled if the user can do so $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count( $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); // Add RC row to the DB $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot, '', $newsize, $revisionId, $patrolled ); // Log auto-patrolled edits if ( $patrolled ) { PatrolLog::record( $rc, true, $user ); } } $user->incEditCount(); $dbw->commit( __METHOD__ ); // Update links, etc. $this->doEditUpdates( $revision, $user, array( 'created' => true ) ); $hook_args = array( &$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision ); ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args ); wfRunHooks( 'PageContentInsertComplete', $hook_args ); } // Do updates right now unless deferral was requested if ( !( $flags & EDIT_DEFER_UPDATES ) ) { DeferredUpdates::doUpdates(); } // Return the new revision (or null) to the caller $status->value['revision'] = $revision; $hook_args = array( &$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ); ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args ); wfRunHooks( 'PageContentSaveComplete', $hook_args ); // Promote user to any groups they meet the criteria for $user->addAutopromoteOnceGroups( 'onEdit' ); wfProfileOut( __METHOD__ ); return $status; }
/** * Show a page view for a page formatted as CSS or JavaScript. To be called by * Article::view() only. * * This exists mostly to serve the deprecated ShowRawCssJs hook (used to customize these views). * It has been replaced by the ContentGetParserOutput hook, which lets you do the same but with * more flexibility. * * @param bool $showCacheHint Whether to show a message telling the user * to clear the browser cache (default: true). */ protected function showCssOrJsPage($showCacheHint = true) { $outputPage = $this->getContext()->getOutput(); if ($showCacheHint) { $dir = $this->getContext()->getLanguage()->getDir(); $lang = $this->getContext()->getLanguage()->getHtmlCode(); $outputPage->wrapWikiMsg("<div id='mw-clearyourcache' lang='{$lang}' dir='{$dir}' class='mw-content-{$dir}'>\n\$1\n</div>", 'clearyourcache'); } $this->fetchContentObject(); if ($this->mContentObject) { // Give hooks a chance to customise the output if (ContentHandler::runLegacyHooks('ShowRawCssJs', array($this->mContentObject, $this->getTitle(), $outputPage))) { // If no legacy hooks ran, display the content of the parser output, including RL modules, // but excluding metadata like categories and language links $po = $this->mContentObject->getParserOutput($this->getTitle()); $outputPage->addParserOutputContent($po); } } }
/** * Show the new revision of the page. */ function renderNewRevision() { wfProfileIn(__METHOD__); $out = $this->getOutput(); $revHeader = $this->getRevisionHeader($this->mNewRev); # Add "current version as of X" title $out->addHTML("<hr class='diff-hr' />\n\t\t<h2 class='diff-currentversion-title'>{$revHeader}</h2>\n"); # Page content may be handled by a hooked call instead... if (wfRunHooks('ArticleContentOnDiff', array($this, $out))) { $this->loadNewText(); $out->setRevisionId($this->mNewid); $out->setRevisionTimestamp($this->mNewRev->getTimestamp()); $out->setArticleFlag(true); // NOTE: only needed for B/C: custom rendering of JS/CSS via hook if ($this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage()) { // Stolen from Article::view --AG 2007-10-11 // Give hooks a chance to customise the output // @TODO: standardize this crap into one function if (ContentHandler::runLegacyHooks('ShowRawCssJs', array($this->mNewContent, $this->mNewPage, $out))) { // NOTE: deprecated hook, B/C only // use the content object's own rendering $po = $this->mNewRev->getContent()->getParserOutput($this->mNewRev->getTitle(), $this->mNewRev->getId()); $out->addHTML($po->getText()); } } elseif (!wfRunHooks('ArticleContentViewCustom', array($this->mNewContent, $this->mNewPage, $out))) { // Handled by extension } elseif (!ContentHandler::runLegacyHooks('ArticleViewCustom', array($this->mNewContent, $this->mNewPage, $out))) { // NOTE: deprecated hook, B/C only // Handled by extension } else { // Normal page if ($this->getTitle()->equals($this->mNewPage)) { // If the Title stored in the context is the same as the one // of the new revision, we can use its associated WikiPage // object. $wikiPage = $this->getWikiPage(); } else { // Otherwise we need to create our own WikiPage object $wikiPage = WikiPage::factory($this->mNewPage); } $parserOutput = $this->getParserOutput($wikiPage, $this->mNewRev); # Also try to load it as a redirect $rt = $this->mNewContent->getRedirectTarget(); if ($rt) { $article = Article::newFromTitle($this->mNewPage, $this->getContext()); $out->addHTML($article->viewRedirect($rt)); # WikiPage::getParserOutput() should not return false, but just in case if ($parserOutput) { # Show categories etc. $out->addParserOutputNoText($parserOutput); } } else { if ($parserOutput) { $out->addParserOutput($parserOutput); } } } } # Add redundant patrol link on bottom... $out->addHTML($this->markPatrolledLink()); wfProfileOut(__METHOD__); }
public function testRunLegacyHooks() { Hooks::register('testRunLegacyHooks', __CLASS__ . '::dummyHookHandler'); $content = new WikitextContent('test text'); $ok = ContentHandler::runLegacyHooks('testRunLegacyHooks', array('foo', &$content, 'bar'), false); $this->assertTrue($ok, "runLegacyHooks should have returned true"); $this->assertEquals("TEST TEXT", $content->getNativeData()); }
/** * @param Content $content Pre-save transform content * @param integer $flags * @param User $user * @param string $summary * @param array $meta * @return Status * @throws DBUnexpectedError * @throws Exception * @throws FatalError * @throws MWException */ private function doCreate(Content $content, $flags, User $user, $summary, array $meta) { global $wgUseRCPatrol, $wgUseNPPatrol; $status = Status::newGood(['new' => true, 'revision' => null]); $now = wfTimestampNow(); $newsize = $content->getSize(); $prepStatus = $content->prepareSave($this, $flags, $meta['oldId'], $user); $status->merge($prepStatus); if (!$status->isOK()) { return $status; } $dbw = wfGetDB(DB_MASTER); $dbw->startAtomic(__METHOD__); // Add the page record unless one already exists for the title $newid = $this->insertOn($dbw); if ($newid === false) { $dbw->endAtomic(__METHOD__); // nothing inserted $status->fatal('edit-already-exists'); return $status; // nothing done } // At this point we are now comitted to returning an OK // status unless some DB query error or other exception comes up. // This way callers don't have to call rollback() if $status is bad // unless they actually try to catch exceptions (which is rare). // @TODO: pass content object?! $revision = new Revision(['page' => $newid, 'title' => $this->mTitle, 'comment' => $summary, 'minor_edit' => $meta['minor'], 'text' => $meta['serialized'], 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $meta['serialFormat']]); // Save the revision text... $revisionId = $revision->insertOn($dbw); // Update the page record with revision data if (!$this->updateRevisionOn($dbw, $revision, 0)) { throw new MWException("Failed to update page row to use new revision."); } Hooks::run('NewRevisionFromEditComplete', [$this, $revision, false, $user]); // Update recentchanges if (!($flags & EDIT_SUPPRESS_RC)) { // Mark as patrolled if the user can do so $patrolled = ($wgUseRCPatrol || $wgUseNPPatrol) && !count($this->mTitle->getUserPermissionsErrors('autopatrol', $user)); // Add RC row to the DB RecentChange::notifyNew($now, $this->mTitle, $revision->isMinor(), $user, $summary, $meta['bot'], '', $newsize, $revisionId, $patrolled, $meta['tags']); } $user->incEditCount(); $dbw->endAtomic(__METHOD__); $this->mTimestamp = $now; // Return the new revision to the caller $status->value['revision'] = $revision; // Do secondary updates once the main changes have been committed... DeferredUpdates::addUpdate(new AtomicSectionUpdate($dbw, __METHOD__, function () use($revision, &$user, $content, $summary, &$flags, $meta, &$status) { // Update links, etc. $this->doEditUpdates($revision, $user, ['created' => true]); // Trigger post-create hook $params = [&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision]; ContentHandler::runLegacyHooks('ArticleInsertComplete', $params, '1.21'); Hooks::run('PageContentInsertComplete', $params); // Trigger post-save hook $params = array_merge($params, [&$status, $meta['baseRevId']]); ContentHandler::runLegacyHooks('ArticleSaveComplete', $params, '1.21'); Hooks::run('PageContentSaveComplete', $params); }), DeferredUpdates::PRESEND); return $status; }
/** * Show a page view for a page formatted as CSS or JavaScript. To be called by * Article::view() only. * * This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these * page views. * * @param bool $showCacheHint whether to show a message telling the user to clear the browser cache (default: true). */ protected function showCssOrJsPage($showCacheHint = true) { $outputPage = $this->getContext()->getOutput(); if ($showCacheHint) { $dir = $this->getContext()->getLanguage()->getDir(); $lang = $this->getContext()->getLanguage()->getCode(); $outputPage->wrapWikiMsg("<div id='mw-clearyourcache' lang='{$lang}' dir='{$dir}' class='mw-content-{$dir}'>\n\$1\n</div>", 'clearyourcache'); } // Give hooks a chance to customise the output if (ContentHandler::runLegacyHooks('ShowRawCssJs', array($this->fetchContentObject(), $this->getTitle(), $outputPage))) { $po = $this->mContentObject->getParserOutput($this->getTitle()); $outputPage->addHTML($po->getText()); } }
/** * Get the rendered text for previewing. * @throws MWException * @return string */ function getPreviewText() { global $wgOut, $wgUser, $wgRawHtml, $wgLang; global $wgAllowUserCss, $wgAllowUserJs; wfProfileIn(__METHOD__); 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); } wfProfileOut(__METHOD__); return $parsedNote; } $note = ''; try { $content = $this->toEditContent($this->textbox1); $previewHTML = ''; if (!wfRunHooks('AlternateEditPreview', array($this, &$content, &$previewHTML, &$this->mParserOutput))) { wfProfileOut(__METHOD__); 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(); } else { $note = wfMessage('session_fail_preview')->plain(); } } elseif ($this->incompleteForm) { $note = wfMessage('edit_form_incomplete')->plain(); } else { $note = wfMessage('previewnote')->plain() . ' ' . $continueEditing; } $parserOptions = $this->mArticle->makeParserOptions($this->mArticle->getContext()); $parserOptions->setEditSection(false); $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); wfRunHooks('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 $content = $content->preSaveTransform($this->mTitle, $wgUser, $parserOptions); $parserOutput = $content->getParserOutput($this->getArticle()->getTitle(), null, $parserOptions); $previewHTML = $parserOutput->getText(); $this->mParserOutput = $parserOutput; $wgOut->addParserOutputMetadata($parserOutput); if (count($parserOutput->getWarnings())) { $note .= "\n\n" . implode("\n\n", $parserOutput->getWarnings()); } } 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); wfProfileOut(__METHOD__); return $previewhead . $previewHTML . $this->previewTextAfterContent; }
/** * Change an existing article or create a new article. Updates RC and all necessary caches, * optionally via the deferred update array. * * @param Content $content New content * @param string $summary Edit summary * @param int $flags Bitfield: * EDIT_NEW * Article is known or assumed to be non-existent, create a new one * EDIT_UPDATE * Article is known or assumed to be pre-existing, update it * EDIT_MINOR * Mark this edit minor, if the user is allowed to do so * EDIT_SUPPRESS_RC * Do not log the change in recentchanges * EDIT_FORCE_BOT * Mark the edit a "bot" edit regardless of user rights * EDIT_AUTOSUMMARY * Fill in blank summaries with generated text where possible * * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the * article will be detected. If EDIT_UPDATE is specified and the article * doesn't exist, the function will return an edit-gone-missing error. If * EDIT_NEW is specified and the article does exist, an edit-already-exists * error will be returned. These two conditions are also possible with * auto-detection due to MediaWiki's performance-optimised locking strategy. * * @param bool|int $baseRevId The revision ID this edit was based off, if any. * This is not the parent revision ID, rather the revision ID for older * content used as the source for a rollback, for example. * @param User $user The user doing the edit * @param string $serialFormat Format for storing the content in the * database. * * @throws MWException * @return Status Possible errors: * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't * set the fatal flag of $status. * edit-gone-missing: In update mode, but the article didn't exist. * edit-conflict: In update mode, the article changed unexpectedly. * edit-no-change: Warning that the text was the same as before. * edit-already-exists: In creation mode, but the article already exists. * * Extensions may define additional errors. * * $return->value will contain an associative array with members as follows: * new: Boolean indicating if the function attempted to create a new article. * revision: The revision object for the inserted revision, or null. * * @since 1.21 * @throws MWException */ public function doEditContent(Content $content, $summary, $flags = 0, $baseRevId = false, User $user = null, $serialFormat = null) { global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol; // Low-level sanity check if ($this->mTitle->getText() === '') { throw new MWException('Something is trying to edit an article with an empty title'); } if (!$content->getContentHandler()->canBeUsedOn($this->getTitle())) { return Status::newFatal('content-not-allowed-here', ContentHandler::getLocalizedName($content->getModel()), $this->getTitle()->getPrefixedText()); } $user = is_null($user) ? $wgUser : $user; $status = Status::newGood(array()); // Load the data from the master database if needed. // The caller may already loaded it from the master or even loaded it using // SELECT FOR UPDATE, so do not override that using clear(). $this->loadPageData('fromdbmaster'); $flags = $this->checkFlags($flags); // handle hook $hook_args = array(&$this, &$user, &$content, &$summary, $flags & EDIT_MINOR, null, null, &$flags, &$status); if (!Hooks::run('PageContentSave', $hook_args) || !ContentHandler::runLegacyHooks('ArticleSave', $hook_args)) { wfDebug(__METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n"); if ($status->isOK()) { $status->fatal('edit-hook-aborted'); } return $status; } // Silently ignore EDIT_MINOR if not allowed $isminor = $flags & EDIT_MINOR && $user->isAllowed('minoredit'); $bot = $flags & EDIT_FORCE_BOT; $old_revision = $this->getRevision(); // current revision $old_content = $this->getContent(Revision::RAW); // current revision's content $oldsize = $old_content ? $old_content->getSize() : 0; $oldid = $this->getLatest(); $oldIsRedirect = $this->isRedirect(); $oldcountable = $this->isCountable(); $handler = $content->getContentHandler(); // Provide autosummaries if one is not provided and autosummaries are enabled. if ($wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '') { if (!$old_content) { $old_content = null; } $summary = $handler->getAutosummary($old_content, $content, $flags); } $editInfo = $this->prepareContentForEdit($content, null, $user, $serialFormat); $serialized = $editInfo->pst; /** * @var Content $content */ $content = $editInfo->pstContent; $newsize = $content->getSize(); $dbw = wfGetDB(DB_MASTER); $now = wfTimestampNow(); if ($flags & EDIT_UPDATE) { // Update article, but only if changed. $status->value['new'] = false; if (!$oldid) { // Article gone missing wfDebug(__METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n"); $status->fatal('edit-gone-missing'); return $status; } elseif (!$old_content) { // Sanity check for bug 37225 throw new MWException("Could not find text for current revision {$oldid}."); } $revision = new Revision(array('page' => $this->getId(), 'title' => $this->getTitle(), 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'parent_id' => $oldid, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialFormat)); // XXX: pass content object?! $changed = !$content->equals($old_content); if ($changed) { $prepStatus = $content->prepareSave($this, $flags, $oldid, $user); $status->merge($prepStatus); if (!$status->isOK()) { return $status; } $dbw->begin(__METHOD__); // Get the latest page_latest value while locking it. // Do a CAS style check to see if it's the same as when this method // started. If it changed then bail out before touching the DB. $latestNow = $this->lockAndGetLatest(); if ($latestNow != $oldid) { $dbw->commit(__METHOD__); // Page updated or deleted in the mean time $status->fatal('edit-conflict'); return $status; } // At this point we are now comitted to returning an OK // status unless some DB query error or other exception comes up. // This way callers don't have to call rollback() if $status is bad // unless they actually try to catch exceptions (which is rare). $revisionId = $revision->insertOn($dbw); // Update page_latest and friends to reflect the new revision if (!$this->updateRevisionOn($dbw, $revision, null, $oldIsRedirect)) { $dbw->rollback(__METHOD__); throw new MWException("Failed to update page row to use new revision."); } Hooks::run('NewRevisionFromEditComplete', array($this, $revision, $baseRevId, $user)); // Update recentchanges if (!($flags & EDIT_SUPPRESS_RC)) { // Mark as patrolled if the user can do so $patrolled = $wgUseRCPatrol && !count($this->mTitle->getUserPermissionsErrors('autopatrol', $user)); // Add RC row to the DB RecentChange::notifyEdit($now, $this->mTitle, $isminor, $user, $summary, $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize, $revisionId, $patrolled); } $user->incEditCount(); $dbw->commit(__METHOD__); $this->mTimestamp = $now; } else { // Bug 32948: revision ID must be set to page {{REVISIONID}} and // related variables correctly $revision->setId($this->getLatest()); } // Update links tables, site stats, etc. $this->doEditUpdates($revision, $user, array('changed' => $changed, 'oldcountable' => $oldcountable, 'oldrevision' => $old_revision)); if (!$changed) { $status->warning('edit-no-change'); $revision = null; // Update page_touched, this is usually implicit in the page update // Other cache updates are done in onArticleEdit() $this->mTitle->invalidateCache($now); } } else { // Create new article $status->value['new'] = true; $prepStatus = $content->prepareSave($this, $flags, $oldid, $user); $status->merge($prepStatus); if (!$status->isOK()) { return $status; } $dbw->begin(__METHOD__); // Add the page record unless one already exists for the title $newid = $this->insertOn($dbw); if ($newid === false) { $dbw->commit(__METHOD__); // nothing inserted $status->fatal('edit-already-exists'); return $status; // nothing done } // At this point we are now comitted to returning an OK // status unless some DB query error or other exception comes up. // This way callers don't have to call rollback() if $status is bad // unless they actually try to catch exceptions (which is rare). // Save the revision text... $revision = new Revision(array('page' => $newid, 'title' => $this->getTitle(), 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialFormat)); $revisionId = $revision->insertOn($dbw); // Bug 37225: use accessor to get the text as Revision may trim it $content = $revision->getContent(); // sanity; get normalized version if ($content) { $newsize = $content->getSize(); } // Update the page record with revision data if (!$this->updateRevisionOn($dbw, $revision, 0)) { $dbw->rollback(__METHOD__); throw new MWException("Failed to update page row to use new revision."); } Hooks::run('NewRevisionFromEditComplete', array($this, $revision, false, $user)); // Update recentchanges if (!($flags & EDIT_SUPPRESS_RC)) { // Mark as patrolled if the user can do so $patrolled = ($wgUseRCPatrol || $wgUseNPPatrol) && !count($this->mTitle->getUserPermissionsErrors('autopatrol', $user)); // Add RC row to the DB RecentChange::notifyNew($now, $this->mTitle, $isminor, $user, $summary, $bot, '', $newsize, $revisionId, $patrolled); } $user->incEditCount(); $dbw->commit(__METHOD__); $this->mTimestamp = $now; // Update links, etc. $this->doEditUpdates($revision, $user, array('created' => true, 'oldrevision' => $old_revision)); $hook_args = array(&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision); ContentHandler::runLegacyHooks('ArticleInsertComplete', $hook_args); Hooks::run('PageContentInsertComplete', $hook_args); } // Return the new revision (or null) to the caller $status->value['revision'] = $revision; $hook_args = array(&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId); ContentHandler::runLegacyHooks('ArticleSaveComplete', $hook_args); Hooks::run('PageContentSaveComplete', $hook_args); // Promote user to any groups they meet the criteria for DeferredUpdates::addCallableUpdate(function () use($user) { $user->addAutopromoteOnceGroups('onEdit'); $user->addAutopromoteOnceGroups('onView'); // b/c }); return $status; }
/** * Get the rendered text for previewing. * @throws MWException * @return string */ function getPreviewText() { global $wgOut, $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'>" . $this->context->msg('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', [$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() . ' ' . $this->context->msg('continue-editing')->text() . ']]</span>'; if ($this->mTriedSave && !$this->mTokenOk) { if ($this->mTokenOkExceptSuffix) { $note = $this->context->msg('token_suffix_mismatch')->plain(); $stats->increment('edit.failures.bad_token'); } else { $note = $this->context->msg('session_fail_preview')->plain(); $stats->increment('edit.failures.session_loss'); } } elseif ($this->incompleteForm) { $note = $this->context->msg('edit_form_incomplete')->plain(); if ($this->mTriedSave) { $stats->increment('edit.failures.incomplete_form'); } } else { $note = $this->context->msg('previewnote')->plain() . ' ' . $continueEditing; } # 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'>" . $this->context->msg("{$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 = [$this, &$content]; ContentHandler::runLegacyHooks('EditPageGetPreviewText', $hook_args, '1.25'); Hooks::run('EditPageGetPreviewContent', $hook_args); $parserResult = $this->doPreviewParse($content); $parserOutput = $parserResult['parserOutput']; $previewHTML = $parserResult['html']; $this->mParserOutput = $parserOutput; $wgOut->addParserOutputMetadata($parserOutput); if (count($parserOutput->getWarnings())) { $note .= "\n\n" . implode("\n\n", $parserOutput->getWarnings()); } } catch (MWContentSerializationException $ex) { $m = $this->context->msg('content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage()); $note .= "\n\n" . $m->parse(); $previewHTML = ''; } if ($this->isConflict) { $conflict = '<h2 id="mw-previewconflict">' . $this->context->msg('previewconflict')->escaped() . "</h2>\n"; } else { $conflict = '<hr />'; } $previewhead = "<div class='previewnote'>\n" . '<h2 id="mw-previewheader">' . $this->context->msg('preview')->escaped() . "</h2>" . $wgOut->parse($note, true, true) . $conflict . "</div>\n"; $pageViewLang = $this->mTitle->getPageViewLanguage(); $attribs = ['lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(), 'class' => 'mw-content-' . $pageViewLang->getDir()]; $previewHTML = Html::rawElement('div', $attribs, $previewHTML); return $previewhead . $previewHTML . $this->previewTextAfterContent; }
/** * Show the new revision of the page. */ function renderNewRevision() { wfProfileIn(__METHOD__); $out = $this->getOutput(); $revHeader = $this->getRevisionHeader($this->mNewRev); # Add "current version as of X" title $out->addHTML("<hr class='diff-hr' />\n\t\t<h2 class='diff-currentversion-title'>{$revHeader}</h2>\n"); # Page content may be handled by a hooked call instead... # @codingStandardsIgnoreStart Ignoring long lines. if (wfRunHooks('ArticleContentOnDiff', array($this, $out))) { $this->loadNewText(); $out->setRevisionId($this->mNewid); $out->setRevisionTimestamp($this->mNewRev->getTimestamp()); $out->setArticleFlag(true); // NOTE: only needed for B/C: custom rendering of JS/CSS via hook if ($this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage()) { // Stolen from Article::view --AG 2007-10-11 // Give hooks a chance to customise the output // @todo standardize this crap into one function if (ContentHandler::runLegacyHooks('ShowRawCssJs', array($this->mNewContent, $this->mNewPage, $out))) { // NOTE: deprecated hook, B/C only // use the content object's own rendering $cnt = $this->mNewRev->getContent(); $po = $cnt ? $cnt->getParserOutput($this->mNewRev->getTitle(), $this->mNewRev->getId()) : null; $txt = $po ? $po->getText() : ''; $out->addHTML($txt); } } elseif (!wfRunHooks('ArticleContentViewCustom', array($this->mNewContent, $this->mNewPage, $out))) { // Handled by extension } elseif (!ContentHandler::runLegacyHooks('ArticleViewCustom', array($this->mNewContent, $this->mNewPage, $out))) { // NOTE: deprecated hook, B/C only // Handled by extension } else { // Normal page if ($this->getTitle()->equals($this->mNewPage)) { // If the Title stored in the context is the same as the one // of the new revision, we can use its associated WikiPage // object. $wikiPage = $this->getWikiPage(); } else { // Otherwise we need to create our own WikiPage object $wikiPage = WikiPage::factory($this->mNewPage); } $parserOutput = $this->getParserOutput($wikiPage, $this->mNewRev); # WikiPage::getParserOutput() should not return false, but just in case if ($parserOutput) { // WIKIHOW - change parser output here if (!wfRunHooks('DifferenceEngineRenderRevisionAddParserOutput', array($this, &$out, $parserOutput, $wikiPage))) { $out->addParserOutput($parserOutput); } } } } # @codingStandardsIgnoreEnd # Add redundant patrol link on bottom... // WIKIHOW - added hook here to optionally not show the final patrolled link if (wfRunHooks('DifferenceEngineRenderRevisionShowFinalPatrolLink', array())) { $out->addHTML($this->markPatrolledLink()); } wfProfileOut(__METHOD__); }