X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FEditPage.php;h=47912cb8c2c3f09b07184b777dcdcf0e3b998e62;hb=a45caa8469bc6091d2161fde212c454cc0f0b970;hp=05e0ac0ee97e137af0cc490e66e3265e3e818086;hpb=1eceaa6c20730b987b92f1d505b9cf68737a95a9;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/EditPage.php b/includes/EditPage.php index 05e0ac0ee9..47912cb8c2 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -201,6 +201,8 @@ class EditPage { /** @var Article */ public $mArticle; + /** @var WikiPage */ + private $page; /** @var Title */ public $mTitle; @@ -399,6 +401,7 @@ class EditPage { */ public function __construct( Article $article ) { $this->mArticle = $article; + $this->page = $article->getPage(); // model object $this->mTitle = $article->getTitle(); $this->contentModel = $this->mTitle->getContentModel(); @@ -537,6 +540,20 @@ class EditPage { return; } + $revision = $this->mArticle->getRevisionFetched(); + // Disallow editing revisions with content models different from the current one + if ( $revision && $revision->getContentModel() !== $this->contentModel ) { + $this->displayViewSourcePage( + $this->getContentObject(), + wfMessage( + 'contentmodelediterror', + $revision->getContentModel(), + $this->contentModel + )->plain() + ); + return; + } + $this->isConflict = false; // css / js subpages of user pages get a special treatment $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage(); @@ -647,6 +664,20 @@ class EditPage { throw new PermissionsError( $action, $permErrors ); } + $this->displayViewSourcePage( + $content, + $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) + ); + } + + /** + * Display a read-only View Source page + * @param Content $content content object + * @param string $errorMessage additional wikitext error message to display + */ + protected function displayViewSourcePage( Content $content, $errorMessage = '' ) { + global $wgOut; + Hooks::run( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); @@ -658,8 +689,10 @@ class EditPage { $wgOut->addHTML( $this->editFormPageTop ); $wgOut->addHTML( $this->editFormTextTop ); - $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) ); - $wgOut->addHTML( "
\n" ); + if ( $errorMessage !== '' ) { + $wgOut->addWikiText( $errorMessage ); + $wgOut->addHTML( "
\n" ); + } # If the user made changes, preserve them when showing the markup # (This happens when a user is blocked during edit, for instance) @@ -667,7 +700,13 @@ class EditPage { $text = $this->textbox1; $wgOut->addWikiMsg( 'viewyourtext' ); } else { - $text = $this->toEditText( $content ); + try { + $text = $this->toEditText( $content ); + } catch ( MWException $e ) { + # Serialize using the default format if the content model is not supported + # (e.g. for an old revision with a different model) + $text = $content->serialize(); + } $wgOut->addWikiMsg( 'viewsourcetext' ); } @@ -966,6 +1005,7 @@ class EditPage { * for saving, preview parsing and so on... * * @param WebRequest $request + * @return string|null */ protected function importContentFormData( &$request ) { return; // Don't do anything, EditPage already extracted wpTextbox1 @@ -978,9 +1018,9 @@ class EditPage { */ function initialiseForm() { global $wgUser; - $this->edittime = $this->mArticle->getTimestamp(); + $this->edittime = $this->page->getTimestamp(); - $content = $this->getContentObject( false ); #TODO: track content object?! + $content = $this->getContentObject( false ); # TODO: track content object?! if ( $content === false ) { return false; } @@ -1062,13 +1102,13 @@ class EditPage { !$undorev->isDeleted( Revision::DELETED_TEXT ) && !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) { - $content = $this->mArticle->getUndoContent( $undorev, $oldrev ); + $content = $this->page->getUndoContent( $undorev, $oldrev ); if ( $content === false ) { # Warn the user that something went wrong $undoMsg = 'failure'; } else { - $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW ); + $oldContent = $this->page->getContent( Revision::RAW ); $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts ); @@ -1162,6 +1202,26 @@ class EditPage { return $content; } + /** + * Get the edit's parent revision ID + * + * The "parent" revision is the ancestor that should be recorded in this + * page's revision history. It is either the revision ID of the in-memory + * article content, or in the case of a 3-way merge in order to rebase + * across a recoverable edit conflict, the ID of the newer revision to + * which we have rebased this page. + * + * @since 1.27 + * @return int Revision ID + */ + public function getParentRevId() { + if ( $this->parentRevId ) { + return $this->parentRevId; + } else { + return $this->mArticle->getRevIdFetched(); + } + } + /** * Get the current content of the page. This is basically similar to * WikiPage::getContent( Revision::RAW ) except that when the page doesn't exist an empty @@ -1171,7 +1231,7 @@ class EditPage { * @return Content */ protected function getCurrentContent() { - $rev = $this->mArticle->getRevision(); + $rev = $this->page->getRevision(); $content = $rev ? $rev->getContent( Revision::RAW ) : null; if ( $content === false || $content === null ) { @@ -1228,7 +1288,7 @@ class EditPage { $title = Title::newFromText( $preload ); # Check for existence to avoid getting MediaWiki:Noarticletext if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) { - //TODO: somehow show a warning to the user! + // TODO: somehow show a warning to the user! return $handler->makeEmptyContent(); } @@ -1237,7 +1297,7 @@ class EditPage { $title = $page->getRedirectTarget(); # Same as before if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) { - //TODO: somehow show a warning to the user! + // TODO: somehow show a warning to the user! return $handler->makeEmptyContent(); } $page = WikiPage::factory( $title ); @@ -1247,7 +1307,7 @@ class EditPage { $content = $page->getContent( Revision::RAW ); if ( !$content ) { - //TODO: somehow show a warning to the user! + // TODO: somehow show a warning to the user! return $handler->makeEmptyContent(); } @@ -1255,7 +1315,7 @@ class EditPage { $converted = $content->convert( $handler->getModelID() ); if ( !$converted ) { - //TODO: somehow show a warning to the user! + // TODO: somehow show a warning to the user! wfDebug( "Attempt to preload incompatible content: " . "can't convert " . $content->getModel() . " to " . $handler->getModelID() ); @@ -1301,7 +1361,7 @@ class EditPage { * @param int $statusValue The status value (to check for new article status) */ protected function setPostEditCookie( $statusValue ) { - $revisionId = $this->mArticle->getLatest(); + $revisionId = $this->page->getLatest(); $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId; $val = 'saved'; @@ -1312,7 +1372,7 @@ class EditPage { } $response = RequestContext::getMain()->getRequest()->response(); - $response->setcookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, array( + $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, array( 'httpOnly' => false, ) ); } @@ -1724,8 +1784,8 @@ class EditPage { # Load the page data from the master. If anything changes in the meantime, # we detect it by using page_latest like a token in a 1 try compare-and-swap. - $this->mArticle->loadPageData( 'fromdbmaster' ); - $new = !$this->mArticle->exists(); + $this->page->loadPageData( 'fromdbmaster' ); + $new = !$this->page->exists(); if ( $new ) { // Late check for create permission, just in case *PARANOIA* @@ -1777,19 +1837,19 @@ class EditPage { # Article exists. Check for edit conflict. - $this->mArticle->clear(); # Force reload of dates, etc. - $timestamp = $this->mArticle->getTimestamp(); + $this->page->clear(); # Force reload of dates, etc. + $timestamp = $this->page->getTimestamp(); wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" ); if ( $timestamp != $this->edittime ) { $this->isConflict = true; if ( $this->section == 'new' ) { - if ( $this->mArticle->getUserText() == $wgUser->getName() && - $this->mArticle->getComment() == $this->newSectionSummary() + if ( $this->page->getUserText() == $wgUser->getName() && + $this->page->getComment() == $this->newSectionSummary() ) { // Probably a duplicate submission of a new comment. - // This can happen when squid resends a request after + // This can happen when CDN resends a request after // a timeout but the first one actually went through. wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" ); @@ -1824,7 +1884,7 @@ class EditPage { . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'" . " (article time '{$timestamp}')\n" ); - $content = $this->mArticle->replaceSectionContent( + $content = $this->page->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, @@ -1832,7 +1892,7 @@ class EditPage { ); } else { wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" ); - $content = $this->mArticle->replaceSectionContent( + $content = $this->page->replaceSectionContent( $this->section, $textbox_content, $sectionTitle @@ -1942,12 +2002,12 @@ class EditPage { return $status; } - $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | + $flags = EDIT_AUTOSUMMARY | ( $new ? EDIT_NEW : EDIT_UPDATE ) | ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) | ( $bot ? EDIT_FORCE_BOT : 0 ); - $doEditStatus = $this->mArticle->doEditContent( + $doEditStatus = $this->page->doEditContent( $content, $this->summary, $flags, @@ -2005,7 +2065,7 @@ class EditPage { } /** - * @param Title $title + * @param User $user * @param string $oldModel * @param string $newModel * @param string $reason @@ -2023,26 +2083,26 @@ class EditPage { $log->publish( $logid ); } - /** * Register the change of watch status */ protected function updateWatchlist() { global $wgUser; - if ( $wgUser->isLoggedIn() - && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS ) - ) { - $fname = __METHOD__; - $title = $this->mTitle; - $watch = $this->watchthis; - - // Do this in its own transaction to reduce contention... - $dbw = wfGetDB( DB_MASTER ); - $dbw->onTransactionIdle( function () use ( $dbw, $title, $watch, $wgUser, $fname ) { - WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser ); - } ); + if ( !$wgUser->isLoggedIn() ) { + return; } + + $user = $wgUser; + $title = $this->mTitle; + $watch = $this->watchthis; + // Do this in its own transaction to reduce contention... + DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) { + if ( $watch == $user->isWatched( $title, WatchedItem::IGNORE_USER_RIGHTS ) ) { + return; // nothing to change + } + WatchAction::doWatchOrUnwatch( $watch, $title, $user ); + } ); } /** @@ -2082,6 +2142,8 @@ class EditPage { if ( $result ) { $editContent = $result; + // Update parentRevId to what we just merged. + $this->parentRevId = $currentRevision->getId(); return true; } @@ -2089,7 +2151,9 @@ class EditPage { } /** - * @return Revision + * @note: this method is very poorly named. If the user opened the form with ?oldid=X, + * one might think of X as the "base revision", which is NOT what this returns. + * @return Revision Current version when the edit was started */ function getBaseRevision() { if ( !$this->mBaseRevision ) { @@ -2188,6 +2252,8 @@ class EditPage { } # Use the title defined by DISPLAYTITLE magic word when present + # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text. + # setPageTitle() treats the input as wikitext, which should be safe in either case. $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false; if ( $displayTitle === false ) { $displayTitle = $contextTitle->getPrefixedText(); @@ -2514,7 +2580,7 @@ class EditPage { # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the # user being bounced back more than once in the event that a summary # is not required. - ##### + # #### # For a bit more sophisticated detection of blank summaries, hash the # automatic one and pass that in the hidden field wpAutoSummary. if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) { @@ -2540,8 +2606,7 @@ class EditPage { $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) ); $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) ); - $wgOut->addHTML( Html::hidden( 'parentRevId', - $this->parentRevId ?: $this->mArticle->getRevIdFetched() ) ); + $wgOut->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) ); $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) ); $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) ); @@ -2592,7 +2657,7 @@ class EditPage { Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) ); $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ), - Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) ); + Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) ); $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ), self::getPreviewLimitReport( $this->mParserOutput ) ) ); @@ -2669,7 +2734,7 @@ class EditPage { if ( $this->isConflict ) { $wgOut->wrapWikiMsg( "
\n$1\n
", 'explainconflict' ); - $this->edittime = $this->mArticle->getTimestamp(); + $this->edittime = $this->page->getTimestamp(); } else { if ( $this->section != '' && !$this->isSectionEditSupported() ) { // We use $this->section to much before this and getVal('wgSection') directly in other places @@ -2681,7 +2746,7 @@ class EditPage { if ( $this->section != '' && $this->section != 'new' ) { if ( !$this->summary && !$this->preview && !$this->diff ) { - $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object + $sectionTitle = self::extractSectionTitle( $this->textbox1 ); // FIXME: use Content object if ( $sectionTitle !== false ) { $this->summary = "/* $sectionTitle */ "; } @@ -2810,6 +2875,7 @@ class EditPage { } if ( $this->mTitle->isCascadeProtected() ) { # Is this page under cascading protection from some source pages? + /** @var Title[] $cascadeSources */ list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources(); $notice = "
\n$1\n"; $cascadeSourcesCount = count( $cascadeSources ); @@ -3179,7 +3245,7 @@ HTML $textboxContent = $this->toEditContent( $this->textbox1 ); - $newContent = $this->mArticle->replaceSectionContent( + $newContent = $this->page->replaceSectionContent( $this->section, $textboxContent, $this->summary, $this->edittime ); @@ -3569,7 +3635,7 @@ HTML $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing; } - $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() ); + $parserOptions = $this->page->makeParserOptions( $this->mArticle->getContext() ); $parserOptions->setIsPreview( true ); $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' ); @@ -3641,6 +3707,8 @@ HTML if ( count( $parserOutput->getWarnings() ) ) { $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() ); } + + ScopedCallback::consume( $scopedCallback ); } catch ( MWContentSerializationException $ex ) { $m = wfMessage( 'content-failed-to-parse', @@ -3705,7 +3773,7 @@ HTML $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos ); $showSignature = true; if ( $title ) { - $showSignature = MWNamespace::wantSignatures( $title->getNamespace() ); + $showSignature = MWNamespace::wantSignatures( $title->getNamespace() ); } /**