diff --git a/extension.json b/extension.json index 4a1b956a99..9bcaee6264 100644 --- a/extension.json +++ b/extension.json @@ -194,6 +194,7 @@ "services": [ "RevisionLookup", "TempUserCreator", + "UserFactory", "UserOptionsLookup", "WatchlistManager", "ContentTransformer", diff --git a/includes/ApiVisualEditor.php b/includes/ApiVisualEditor.php index ea7a841607..d6a4c5c731 100644 --- a/includes/ApiVisualEditor.php +++ b/includes/ApiVisualEditor.php @@ -35,10 +35,12 @@ use MediaWiki\Revision\RevisionLookup; use MediaWiki\SpecialPage\SpecialPageFactory; use MediaWiki\Title\Title; use MediaWiki\User\TempUser\TempUserCreator; +use MediaWiki\User\UserFactory; use MediaWiki\User\UserOptionsLookup; use MediaWiki\Watchlist\WatchlistManager; use MessageLocalizer; use RequestContext; +use User; use Wikimedia\ParamValidator\ParamValidator; use WikitextContent; @@ -48,6 +50,7 @@ class ApiVisualEditor extends ApiBase { private RevisionLookup $revisionLookup; private TempUserCreator $tempUserCreator; + private UserFactory $userFactory; private UserOptionsLookup $userOptionsLookup; private WatchlistManager $watchlistManager; private ContentTransformer $contentTransformer; @@ -62,6 +65,7 @@ class ApiVisualEditor extends ApiBase { string $name, RevisionLookup $revisionLookup, TempUserCreator $tempUserCreator, + UserFactory $userFactory, UserOptionsLookup $userOptionsLookup, WatchlistManager $watchlistManager, ContentTransformer $contentTransformer, @@ -77,6 +81,7 @@ class ApiVisualEditor extends ApiBase { $this->setStats( $statsdDataFactory ); $this->revisionLookup = $revisionLookup; $this->tempUserCreator = $tempUserCreator; + $this->userFactory = $userFactory; $this->userOptionsLookup = $userOptionsLookup; $this->watchlistManager = $watchlistManager; $this->contentTransformer = $contentTransformer; @@ -96,6 +101,20 @@ class ApiVisualEditor extends ApiBase { ); } + /** + * @see ApiParse::getUserForPreview + * @return User + */ + private function getUserForPreview() { + $user = $this->getUser(); + if ( $this->tempUserCreator->shouldAutoCreate( $user, 'edit' ) ) { + return $this->userFactory->newUnsavedTempUser( + $this->tempUserCreator->getStashedName( $this->getRequest()->getSession() ) + ); + } + return $user; + } + /** * Run wikitext through the parser's Pre-Save-Transform * @@ -108,7 +127,7 @@ class ApiVisualEditor extends ApiBase { return $this->contentTransformer->preSaveTransform( $content, $title, - $this->getUser(), + $this->getUserForPreview(), $this->wikiPageFactory->newFromTitle( $title )->makeParserOptions( $this->getContext() ) ) ->serialize( 'text/x-wiki' ); diff --git a/modules/ve-mw/ce/nodes/ve.ce.MWSignatureNode.js b/modules/ve-mw/ce/nodes/ve.ce.MWSignatureNode.js index ed13a1e437..a61dd928dc 100644 --- a/modules/ve-mw/ce/nodes/ve.ce.MWSignatureNode.js +++ b/modules/ve-mw/ce/nodes/ve.ce.MWSignatureNode.js @@ -106,45 +106,62 @@ ve.ce.MWSignatureNode.prototype.onTeardown = function () { * @inheritdoc ve.ce.GeneratedContentNode */ ve.ce.MWSignatureNode.prototype.generateContents = function () { - // Parsoid doesn't support pre-save transforms. PHP parser doesn't support Parsoid's - // meta attributes (that may or may not be required). - - // We could try hacking up one (or even both) of these, but just calling the two parsers - // in order seems slightly saner. - - // We must have only one top-level node, this is the easiest way. - var wikitext = '~~~~'; var doc = this.getModel().getDocument(); + var abortable, aborted; + var abortedPromise = ve.createDeferred().reject( 'http', + { textStatus: 'abort', exception: 'abort' } ).promise(); - var deferred = ve.createDeferred(); - var xhr = ve.init.target.getContentApi( doc ).post( { - action: 'parse', - text: wikitext, - contentmodel: 'wikitext', - prop: 'text', - onlypst: true - } ) - .done( function ( resp ) { - var wt = ve.getProp( resp, 'parse', 'text' ); - if ( wt ) { - ve.init.target.parseWikitextFragment( wt, true, doc ).then( function ( response ) { - if ( ve.getProp( response, 'visualeditor', 'result' ) !== 'success' ) { - deferred.reject(); - return; - } + function abort() { + aborted = true; + if ( abortable && abortable.abort ) { + abortable.abort(); + } + } - // Simplified case of template rendering, don't need to worry about filtering etc - deferred.resolve( $( response.visualeditor.content ).contents().toArray() ); - } ); - } else { - deferred.reject(); + // Acquire a temporary user username before previewing, so that signatures + // display the temp user instead of IP user. (T331397) + return mw.user.acquireTempUserName() + .then( function () { + if ( aborted ) { + return abortedPromise; } - } ) - .fail( function () { - deferred.reject(); - } ); - return deferred.promise( { abort: xhr.abort } ); + // We must have only one top-level node, this is the easiest way. + var wikitext = '~~~~'; + + // Parsoid doesn't support pre-save transforms. PHP parser doesn't support Parsoid's + // meta attributes (that may or may not be required). + // We could try hacking up one (or even both) of these, but just calling the two parsers + // in order seems slightly saner. + return ( abortable = ve.init.target.getContentApi( doc ).post( { + action: 'parse', + text: wikitext, + contentmodel: 'wikitext', + prop: 'text', + onlypst: true + } ) ); + } ) + .then( function ( pstResponse ) { + if ( aborted ) { + return abortedPromise; + } + var wikitext = ve.getProp( pstResponse, 'parse', 'text' ); + if ( !wikitext ) { + return ve.createDeferred().reject(); + } + return ( abortable = ve.init.target.parseWikitextFragment( wikitext, true, doc ) ); + } ) + .then( function ( parseResponse ) { + if ( aborted ) { + return abortedPromise; + } + if ( ve.getProp( parseResponse, 'visualeditor', 'result' ) !== 'success' ) { + return ve.createDeferred().reject(); + } + // Simplified case of template rendering, don't need to worry about filtering etc + return $( parseResponse.visualeditor.content ).contents().toArray(); + } ) + .promise( { abort: abort } ); }; /* Registration */ diff --git a/modules/ve-mw/init/targets/ve.init.mw.ArticleTarget.js b/modules/ve-mw/init/targets/ve.init.mw.ArticleTarget.js index 1e413e452a..c3b59c847e 100644 --- a/modules/ve-mw/init/targets/ve.init.mw.ArticleTarget.js +++ b/modules/ve-mw/init/targets/ve.init.mw.ArticleTarget.js @@ -915,14 +915,18 @@ ve.init.mw.ArticleTarget.prototype.onSaveDialogReview = function () { if ( !this.saveDialog.hasDiff ) { this.emit( 'saveReview' ); this.saveDialog.pushPending(); - if ( this.pageExists ) { - // Has no callback, handled via target.showChangesDiff - this.showChanges( this.getDocToSave() ); - } else { - this.serialize( this.getDocToSave() ).then( function ( data ) { - target.onSaveDialogReviewComplete( data.content ); - } ); - } + // Acquire a temporary user username before diffing, so that signatures and + // user-related magic words display the temp user instead of IP user in the diff. (T331397) + mw.user.acquireTempUserName().then( function () { + if ( target.pageExists ) { + // Has no callback, handled via target.showChangesDiff + target.showChanges( target.getDocToSave() ); + } else { + target.serialize( target.getDocToSave() ).then( function ( data ) { + target.onSaveDialogReviewComplete( data.content ); + } ); + } + } ); } else { this.saveDialog.swapPanel( 'review' ); } @@ -952,19 +956,23 @@ ve.init.mw.ArticleTarget.prototype.onSaveDialogPreview = function () { params.variant = mw.config.get( 'wgUserVariant' ); } - api.post( ve.extendObject( params, { - action: 'parse', - title: this.getPageName(), - text: this.getDocToSave(), - pst: true, - preview: true, - sectionpreview: this.section !== null, - disableeditsection: true, - uselang: mw.config.get( 'wgUserLanguage' ), - useskin: mw.config.get( 'skin' ), - mobileformat: OO.ui.isMobile(), - prop: [ 'text', 'categorieshtml', 'displaytitle', 'subtitle', 'modules', 'jsconfigvars' ] - } ) ).then( function ( response ) { + // Acquire a temporary user username before previewing, so that signatures and + // user-related magic words display the temp user instead of IP user in the preview. (T331397) + mw.user.acquireTempUserName().then( function () { + return api.post( ve.extendObject( params, { + action: 'parse', + title: target.getPageName(), + text: target.getDocToSave(), + pst: true, + preview: true, + sectionpreview: target.section !== null, + disableeditsection: true, + uselang: mw.config.get( 'wgUserLanguage' ), + useskin: mw.config.get( 'skin' ), + mobileformat: OO.ui.isMobile(), + prop: [ 'text', 'categorieshtml', 'displaytitle', 'subtitle', 'modules', 'jsconfigvars' ] + } ) ); + } ).then( function ( response ) { target.saveDialog.showPreview( response ); }, function ( errorCode, details ) { target.saveDialog.showPreview( target.extractErrorMessages( details ) ); @@ -1035,14 +1043,18 @@ ve.init.mw.ArticleTarget.prototype.getVisualDiffGeneratorPromise = function () { } if ( mode === 'source' ) { - var newRevPromise = target.getContentApi().post( { - action: 'visualeditor', - paction: 'parse', - page: target.getPageName(), - wikitext: target.getSurface().getDom(), - section: target.section, - stash: 0, - pst: true + // Acquire a temporary user username before diffing, so that signatures and + // user-related magic words display the temp user instead of IP user in the diff. (T331397) + var newRevPromise = mw.user.acquireTempUserName().then( function () { + return target.getContentApi().post( { + action: 'visualeditor', + paction: 'parse', + page: target.getPageName(), + wikitext: target.getSurface().getDom(), + section: target.section, + stash: 0, + pst: true + } ); } ).then( function ( response ) { // Source mode always fetches the whole document, so set section=null to unwrap sections return mw.libs.ve.diffLoader.getModelFromResponse( response, null ); diff --git a/modules/ve-mw/init/targets/ve.init.mw.Target.js b/modules/ve-mw/init/targets/ve.init.mw.Target.js index 3749c8a331..9cda401d68 100644 --- a/modules/ve-mw/init/targets/ve.init.mw.Target.js +++ b/modules/ve-mw/init/targets/ve.init.mw.Target.js @@ -615,13 +615,41 @@ ve.init.mw.Target.prototype.getWikitextFragment = function ( doc, useRevision ) * @return {jQuery.Promise} Abortable promise */ ve.init.mw.Target.prototype.parseWikitextFragment = function ( wikitext, pst, doc ) { - return this.getContentApi( doc ).post( { - action: 'visualeditor', - paction: 'parsefragment', - page: this.getPageName( doc ), - wikitext: wikitext, - pst: pst - } ); + var target = this; + var abortable, aborted; + var abortedPromise = ve.createDeferred().reject( 'http', + { textStatus: 'abort', exception: 'abort' } ).promise(); + + function abort() { + aborted = true; + if ( abortable && abortable.abort ) { + abortable.abort(); + } + } + + // Acquire a temporary user username before previewing or diffing, so that signatures and + // user-related magic words display the temp user instead of IP user in the preview. (T331397) + var tempUserNamePromise; + if ( pst ) { + tempUserNamePromise = mw.user.acquireTempUserName(); + } else { + tempUserNamePromise = ve.createDeferred().resolve( null ); + } + + return tempUserNamePromise + .then( function () { + if ( aborted ) { + return abortedPromise; + } + return ( abortable = target.getContentApi( doc ).post( { + action: 'visualeditor', + paction: 'parsefragment', + page: target.getPageName( doc ), + wikitext: wikitext, + pst: pst + } ) ); + } ) + .promise( { abort: abort } ); }; /**