From: Bartosz DziewoƄski Date: Thu, 5 May 2016 15:20:05 +0000 (-0400) Subject: Refactor upload dialog to make it configurable X-Git-Tag: 1.31.0-rc.0~6958 X-Git-Url: https://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/comptes/ajouter.php?a=commitdiff_plain;h=c57fe1c4a890b360fbf88035c601a1e46130d60a;p=lhc%2Fweb%2Fwiklou.git Refactor upload dialog to make it configurable This aims to solve all the problems and fulfill all the use cases. It allows the dialog to be configured for Wikimedia Commons without hardcoding anything, and it should be flexible enough for third-party use. The default configuration should be sane for any wiki. The file upload dialog can be configured using $wgUploadDialog. See DefaultSettings.php for documentation. Example configuration for Wikimedia Commons: Id56370e2334c8fe34e88180356232b48c244b7c4. Configuration is loaded using ResourceLoaderUploadDialogModule for local uploads or using ApiQuerySiteinfo (action=query&meta=siteinfo) for uploads to a foreign wiki. Custom localisation messages may be loaded using action=query&meta=allmessages. Renamed messages: upload-form-label-own-work-message-local -> upload-form-label-own-work-message-generic-local upload-form-label-not-own-work-message-local -> upload-form-label-not-own-work-message-generic-local upload-form-label-not-own-work-local-local -> upload-form-label-not-own-work-local-generic-local upload-form-label-own-work-message-default -> upload-form-label-own-work-message-generic-foreign upload-form-label-not-own-work-message-default -> upload-form-label-not-own-work-message-generic-foreign upload-form-label-not-own-work-local-default -> upload-form-label-not-own-work-local-generic-foreign Deleted messages, moved to WikimediaMessages in Id2977e19330aeaf854157d4355cd17e5dc72f16a: upload-form-label-own-work-message-shared upload-form-label-not-own-work-message-shared upload-form-label-not-own-work-local-shared Bug: T118097 Bug: T120998 Bug: T121632 Bug: T121633 Bug: T127895 Change-Id: I3017b8f09c27625deb7a92d6f667895b71cc0637 --- diff --git a/RELEASE-NOTES-1.27 b/RELEASE-NOTES-1.27 index f44e8c50bb..7c50e4fd9f 100644 --- a/RELEASE-NOTES-1.27 +++ b/RELEASE-NOTES-1.27 @@ -239,6 +239,8 @@ The following PHP extensions are strongly recommended: like changing a password. ** Two new globals, $wgChangeCredentialsBlacklist and $wgRemoveCredentialsBlacklist can be used to prevent the web UI and the API changing certain authentication data. +* The file upload dialog (available if you install WikiEditor or VisualEditor) + can now be configured using $wgUploadDialog. === External library changes in 1.27 === diff --git a/autoload.php b/autoload.php index 6cfffadfcf..f79bace67f 100644 --- a/autoload.php +++ b/autoload.php @@ -1143,6 +1143,7 @@ $wgAutoloadLocalClasses = [ 'ResourceLoaderSkinModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSkinModule.php', 'ResourceLoaderSpecialCharacterDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php', 'ResourceLoaderStartUpModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderStartUpModule.php', + 'ResourceLoaderUploadDialogModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUploadDialogModule.php', 'ResourceLoaderUserCSSPrefsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php', 'ResourceLoaderUserDefaultsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserDefaultsModule.php', 'ResourceLoaderUserGroupsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserGroupsModule.php', diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 6088e8fad0..0b70d1632b 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -535,6 +535,64 @@ $wgUseInstantCommons = false; */ $wgForeignUploadTargets = []; +/** + * Configuration for file uploads using the embeddable upload dialog + * (https://www.mediawiki.org/wiki/Upload_dialog). + * + * This applies also to foreign uploads to this wiki (the configuration is loaded by remote wikis + * using the action=query&meta=siteinfo API). + * + * See below for documentation of each property. None of the properties may be omitted. + */ +$wgUploadDialog = [ + // Fields to make available in the dialog. `true` means that this field is visible, `false` means + // that it is hidden. The "Name" field can't be hidden. Note that you also have to add the + // matching replacement to the 'filepage' format key below to make use of these. + 'fields' => [ + 'description' => true, + 'date' => false, + 'categories' => false, + ], + // Suffix of localisation messages used to describe the license under which the uploaded file will + // be released. The same value may be set for both 'local' and 'foreign' uploads. + 'licensemessages' => [ + // The 'local' messages are used for local uploads on this wiki: + // * upload-form-label-own-work-message-generic-local + // * upload-form-label-not-own-work-message-generic-local + // * upload-form-label-not-own-work-local-generic-local + 'local' => 'generic-local', + // The 'foreign' messages are used for cross-wiki uploads from other wikis to this wiki: + // * upload-form-label-own-work-message-generic-foreign + // * upload-form-label-not-own-work-message-generic-foreign + // * upload-form-label-not-own-work-local-generic-foreign + 'foreign' => 'generic-foreign', + ], + // Upload comment to use. Available replacements: + // * $HOST - domain name from which a cross-wiki upload originates + // * $PAGENAME - wiki page name from which an upload originates + 'comment' => '', + // Format of the file page wikitext to be generated from the fields input by the user. + 'format' => [ + // Wrapper for the whole page. Available replacements: + // * $DESCRIPTION - file description, as input by the user (only if the 'description' field is + // enabled), wrapped as defined below in the 'description' key + // * $DATE - file creation date, as input by the user (only if the 'date' field is enabled) + // * $SOURCE - as defined below in the 'ownwork' key, may be extended in the future + // * $AUTHOR - linked user name, may be extended in the future + // * $LICENSE - as defined below in the 'license' key, may be extended in the future + // * $CATEGORIES - file categories wikitext, as input by the user (only if the 'categories' + // field is enabled), or if no input, as defined below in the 'uncategorized' key + 'filepage' => '$DESCRIPTION', + // Wrapped for file description. Available replacements: + // * $LANGUAGE - source wiki's content language + // * $TEXT - input by the user + 'description' => '$TEXT', + 'ownwork' => '', + 'license' => '', + 'uncategorized' => '', + ], +]; + /** * File backend structure configuration. * diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index f05556eca3..a08740a43e 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -108,6 +108,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { case 'defaultoptions': $fit = $this->appendDefaultOptions( $p ); break; + case 'uploaddialog': + $fit = $this->appendUploadDialog( $p ); + break; default: ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" ); } @@ -771,6 +774,11 @@ class ApiQuerySiteinfo extends ApiQueryBase { return $this->getResult()->addValue( 'query', $property, $options ); } + public function appendUploadDialog( $property ) { + $config = $this->getConfig()->get( 'UploadDialog' ); + return $this->getResult()->addValue( 'query', $property, $config ); + } + private function formatParserTags( $item ) { return "<{$item}>"; } @@ -838,6 +846,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { 'variables', 'protocols', 'defaultoptions', + 'uploaddialog', ], ApiBase::PARAM_HELP_MSG_PER_VALUE => [], ], diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 43eed78b8b..a802cc7285 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -1127,6 +1127,7 @@ "apihelp-query+siteinfo-paramvalue-prop-variables": "Returns a list of variable IDs.", "apihelp-query+siteinfo-paramvalue-prop-protocols": "Returns a list of protocols that are allowed in external links.", "apihelp-query+siteinfo-paramvalue-prop-defaultoptions": "Returns the default values for user preferences.", + "apihelp-query+siteinfo-paramvalue-prop-uploaddialog": "Returns the upload dialog configuration.", "apihelp-query+siteinfo-param-filteriw": "Return only local or only nonlocal entries of the interwiki map.", "apihelp-query+siteinfo-param-showalldb": "List all database servers, not just the one lagging the most.", "apihelp-query+siteinfo-param-numberingroup": "Lists the number of users in user groups.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index 37bb4b0a32..e6faa21836 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -1050,6 +1050,7 @@ "apihelp-query+siteinfo-paramvalue-prop-variables": "{{doc-apihelp-paramvalue|query+siteinfo|prop|variables}}", "apihelp-query+siteinfo-paramvalue-prop-protocols": "{{doc-apihelp-paramvalue|query+siteinfo|prop|protocols}}", "apihelp-query+siteinfo-paramvalue-prop-defaultoptions": "{{doc-apihelp-paramvalue|query+siteinfo|prop|defaultoptions}}", + "apihelp-query+siteinfo-paramvalue-prop-uploaddialog": "{{doc-apihelp-paramvalue|query+siteinfo|prop|uploaddialog}}", "apihelp-query+siteinfo-param-filteriw": "{{doc-apihelp-param|query+siteinfo|filteriw}}", "apihelp-query+siteinfo-param-showalldb": "{{doc-apihelp-param|query+siteinfo|showalldb}}", "apihelp-query+siteinfo-param-numberingroup": "{{doc-apihelp-param|query+siteinfo|numberingroup}}", diff --git a/includes/resourceloader/ResourceLoaderUploadDialogModule.php b/includes/resourceloader/ResourceLoaderUploadDialogModule.php new file mode 100644 index 0000000000..52e221089f --- /dev/null +++ b/includes/resourceloader/ResourceLoaderUploadDialogModule.php @@ -0,0 +1,42 @@ +getResourceLoader()->getConfig(); + return ResourceLoader::makeConfigSetScript( [ + 'wgUploadDialog' => $config->get( 'UploadDialog' ), + ] ); + } + + public function enableModuleContentVersion() { + return true; + } +} diff --git a/languages/i18n/en.json b/languages/i18n/en.json index c522df8423..8b9fefe63e 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -1528,15 +1528,12 @@ "upload-form-label-own-work": "This is my own work", "upload-form-label-infoform-categories": "Categories", "upload-form-label-infoform-date": "Date", - "upload-form-label-own-work-message-local": "I confirm that I am uploading this file following the terms of service and licensing policies on {{SITENAME}}.", - "upload-form-label-not-own-work-message-local": "If you are not able to upload this file under the policies of {{SITENAME}}, please close this dialog and try another method.", - "upload-form-label-not-own-work-local-local": "You may also want to try [[Special:Upload|the default upload page]].", - "upload-form-label-own-work-message-default": "I understand that I am uploading this file to a shared repository. I confirm that I am doing so following the terms of service and licensing policies there.", - "upload-form-label-not-own-work-message-default": "If you are not able to upload this file under the policies of the shared repository, please close this dialog and try another method.", - "upload-form-label-not-own-work-local-default": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if this file can be uploaded there under their policies.", - "upload-form-label-own-work-message-shared": "I attest that I own the copyright on this file, and agree to irrevocably release this file to Wikimedia Commons under the [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] license, and I agree to the [https://wikimediafoundation.org/wiki/Terms_of_Use Terms of Use].", - "upload-form-label-not-own-work-message-shared": "If you do not own the copyright on this file, or you wish to release it under a different license, consider using the [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard].", - "upload-form-label-not-own-work-local-shared": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if the site allows the upload of this file under their policies.", + "upload-form-label-own-work-message-generic-local": "I confirm that I am uploading this file following the terms of service and licensing policies on {{SITENAME}}.", + "upload-form-label-not-own-work-message-generic-local": "If you are not able to upload this file under the policies of {{SITENAME}}, please close this dialog and try another method.", + "upload-form-label-not-own-work-local-generic-local": "You may also want to try [[Special:Upload|the default upload page]].", + "upload-form-label-own-work-message-generic-foreign": "I understand that I am uploading this file to a shared repository. I confirm that I am doing so following the terms of service and licensing policies there.", + "upload-form-label-not-own-work-message-generic-foreign": "If you are not able to upload this file under the policies of the shared repository, please close this dialog and try another method.", + "upload-form-label-not-own-work-local-generic-foreign": "You may also want to try using [[Special:Upload|the upload page on {{SITENAME}}]], if this file can be uploaded there under their policies.", "backend-fail-stream": "Could not stream file \"$1\".", "backend-fail-backup": "Could not backup file \"$1\".", "backend-fail-notexists": "The file $1 does not exist.", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 8fa5b7439d..21e1344322 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -1707,15 +1707,12 @@ "upload-form-label-own-work": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Label for own work confirmation checkbox", "upload-form-label-infoform-categories": "Label for category selector input\n{{Identical|Category}}", "upload-form-label-infoform-date": "Label for date input\n{{Identical|Date}}", - "upload-form-label-own-work-message-local": "Message shown by local when a user affirms that their file upload to the local wiki follows the terms of service and licensing policies of the local wiki.", - "upload-form-label-not-own-work-message-local": "Message shown by local when a user cannot upload a file to the local wiki.", - "upload-form-label-not-own-work-local-local": "Suggests uploading a file via Special:Upload instead of using whatever method they're currently using.", - "upload-form-label-own-work-message-default": "Message shown by default when a user affirms that they are allowed to upload a file to a remote wiki.", - "upload-form-label-not-own-work-message-default": "Message shown by default when a user cannot upload a file to a remote wiki.", - "upload-form-label-not-own-work-local-default": "Suggests uploading a file locally instead of to a remote wiki.", - "upload-form-label-own-work-message-shared": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Legal message, confirming that the user is allowed to upload the file.", - "upload-form-label-not-own-work-message-shared": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Explains alternatives when the copyright isn't owned by the uploader.", - "upload-form-label-not-own-work-local-shared": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 1.png|thumb]] Message suggesting the user might want to upload a file locally instead of to Wikimedia Commons.", + "upload-form-label-own-work-message-generic-local": "Message shown by local when a user affirms that their file upload to the local wiki follows the terms of service and licensing policies of the local wiki.", + "upload-form-label-not-own-work-message-generic-local": "Message shown by local when a user cannot upload a file to the local wiki.", + "upload-form-label-not-own-work-local-generic-local": "Suggests uploading a file via Special:Upload instead of using whatever method they're currently using.", + "upload-form-label-own-work-message-generic-foreign": "Message shown by default when a user affirms that they are allowed to upload a file to a remote wiki.", + "upload-form-label-not-own-work-message-generic-foreign": "Message shown by default when a user cannot upload a file to a remote wiki.", + "upload-form-label-not-own-work-local-generic-foreign": "Suggests uploading a file locally instead of to a remote wiki.", "backend-fail-stream": "Parameters:\n* $1 - a filename", "backend-fail-backup": "Parameters:\n* $1 - a filename", "backend-fail-notexists": "Parameters:\n* $1 - a filename", diff --git a/resources/Resources.php b/resources/Resources.php index cf2abdb5ef..3fc5801b65 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1169,10 +1169,14 @@ return [ 'upload-foreign-cant-upload', ] ], + 'mediawiki.ForeignStructuredUpload.config' => [ + 'class' => 'ResourceLoaderUploadDialogModule', + ], 'mediawiki.ForeignStructuredUpload' => [ 'scripts' => 'resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js', 'dependencies' => [ 'mediawiki.ForeignUpload', + 'mediawiki.ForeignStructuredUpload.config', ], ], 'mediawiki.Upload.Dialog' => [ @@ -1284,6 +1288,7 @@ return [ 'mediawiki.widgets.CategorySelector', 'mediawiki.widgets.DateInputWidget', 'mediawiki.jqueryMsg', + 'mediawiki.api.messages', 'moment', 'mediawiki.libs.jpegmeta', ], @@ -1291,15 +1296,12 @@ return [ 'upload-form-label-own-work', 'upload-form-label-infoform-categories', 'upload-form-label-infoform-date', - 'upload-form-label-own-work-message-default', - 'upload-form-label-not-own-work-message-default', - 'upload-form-label-not-own-work-local-default', - 'upload-form-label-own-work-message-shared', - 'upload-form-label-not-own-work-message-shared', - 'upload-form-label-not-own-work-local-shared', - 'upload-form-label-own-work-message-local', - 'upload-form-label-not-own-work-message-local', - 'upload-form-label-not-own-work-local-local', + 'upload-form-label-own-work-message-generic-local', + 'upload-form-label-not-own-work-message-generic-local', + 'upload-form-label-not-own-work-local-generic-local', + 'upload-form-label-own-work-message-generic-foreign', + 'upload-form-label-not-own-work-message-generic-foreign', + 'upload-form-label-not-own-work-local-generic-foreign', ], ], 'mediawiki.toc' => [ diff --git a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js index 8509fbca1e..16fec73344 100644 --- a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js +++ b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js @@ -42,24 +42,64 @@ var booklet = this; return mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this ).then( function () { - // Point the CategorySelector to the right wiki - return booklet.upload.getApi().then( - function ( api ) { + return $.when( + // Point the CategorySelector to the right wiki + booklet.upload.getApi().then( function ( api ) { // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything if ( api.apiUrl ) { // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance booklet.categoriesWidget.api = new mw.ForeignApi( api.apiUrl ); } return $.Deferred().resolve(); - }, - function () { - return $.Deferred().resolve(); - } + } ), + // Set up booklet fields and license messages to match configuration + booklet.upload.loadConfig().then( function ( config ) { + var + msgPromise, + isLocal = booklet.upload.target === 'local', + fields = config.fields, + msgs = config.licensemessages[ isLocal ? 'local' : 'foreign' ]; + + // Hide disabled fields + booklet.descriptionField.toggle( !!fields.description ); + booklet.categoriesField.toggle( !!fields.categories ); + booklet.dateField.toggle( !!fields.date ); + // Update form validity + booklet.onInfoFormChange(); + + // Load license messages from the remote wiki if we don't have these messages locally + // (this means that we only load messages from the foreign wiki for custom config) + if ( mw.message( 'upload-form-label-own-work-message-' + msgs ).exists() ) { + msgPromise = $.Deferred().resolve(); + } else { + msgPromise = booklet.upload.apiPromise.then( function ( api ) { + return api.loadMessages( [ + 'upload-form-label-own-work-message-' + msgs, + 'upload-form-label-not-own-work-message-' + msgs, + 'upload-form-label-not-own-work-local-' + msgs + ] ); + } ); + } + + // Update license messages + return msgPromise.then( function () { + booklet.$ownWorkMessage + .msg( 'upload-form-label-own-work-message-' + msgs ) + .find( 'a' ).attr( 'target', '_blank' ); + booklet.$notOwnWorkMessage + .msg( 'upload-form-label-not-own-work-message-' + msgs ) + .find( 'a' ).attr( 'target', '_blank' ); + booklet.$notOwnWorkLocal + .msg( 'upload-form-label-not-own-work-local-' + msgs ) + .find( 'a' ).attr( 'target', '_blank' ); + } ); + } ) ); - }, - function () { - return $.Deferred().resolve(); } + ).then( + null, + // Always resolve, never reject + function () { return $.Deferred().resolve(); } ); }; @@ -80,45 +120,24 @@ * @inheritdoc */ mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () { - var fieldset, $ownWorkMessage, $notOwnWorkMessage, - ownWorkMessage, notOwnWorkMessage, notOwnWorkLocal, - validTargets = mw.config.get( 'wgForeignUploadTargets' ), - target = this.target || validTargets[ 0 ] || 'local', + var fieldset, layout = this; - // upload-form-label-own-work-message-local - // upload-form-label-own-work-message-shared - ownWorkMessage = mw.message( 'upload-form-label-own-work-message-' + target ); - // upload-form-label-not-own-work-message-local - // upload-form-label-not-own-work-message-shared - notOwnWorkMessage = mw.message( 'upload-form-label-not-own-work-message-' + target ); - // upload-form-label-not-own-work-local-local - // upload-form-label-not-own-work-local-shared - notOwnWorkLocal = mw.message( 'upload-form-label-not-own-work-local-' + target ); - - if ( !ownWorkMessage.exists() ) { - ownWorkMessage = mw.message( 'upload-form-label-own-work-message-default' ); - } - if ( !notOwnWorkMessage.exists() ) { - notOwnWorkMessage = mw.message( 'upload-form-label-not-own-work-message-default' ); - } - if ( !notOwnWorkLocal.exists() ) { - notOwnWorkLocal = mw.message( 'upload-form-label-not-own-work-local-default' ); - } - - $ownWorkMessage = $( '

' ).append( ownWorkMessage.parseDom() ) + // These elements are filled with text in #initialize + // TODO Refactor this to be in one place + this.$ownWorkMessage = $( '

' ) .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' ); - $notOwnWorkMessage = $( '

' ).append( - $( '

' ).append( notOwnWorkMessage.parseDom() ), - $( '

' ).append( notOwnWorkLocal.parseDom() ) - ); - $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' ).attr( 'target', '_blank' ); + this.$notOwnWorkMessage = $( '

' ); + this.$notOwnWorkLocal = $( '

' ); this.selectFileWidget = new OO.ui.SelectFileWidget( { showDropTarget: true } ); this.messageLabel = new OO.ui.LabelWidget( { - label: $notOwnWorkMessage + label: $( '

' ).append( + this.$notOwnWorkMessage, + this.$notOwnWorkLocal + ) } ); this.ownWorkCheckbox = new OO.ui.CheckboxInputWidget().on( 'change', function ( on ) { layout.messageLabel.toggle( !on ); @@ -133,7 +152,7 @@ align: 'inline', label: $( '
' ).append( $( '

' ).text( mw.msg( 'upload-form-label-own-work' ) ), - $ownWorkMessage + this.$ownWorkMessage ) } ), new OO.ui.FieldLayout( this.messageLabel, { @@ -210,30 +229,35 @@ mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow } ); + this.filenameField = new OO.ui.FieldLayout( this.filenameWidget, { + label: mw.msg( 'upload-form-label-infoform-name' ), + align: 'top', + classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ], + notices: [ mw.msg( 'upload-form-label-infoform-name-tooltip' ) ] + } ); + this.descriptionField = new OO.ui.FieldLayout( this.descriptionWidget, { + label: mw.msg( 'upload-form-label-infoform-description' ), + align: 'top', + classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ], + notices: [ mw.msg( 'upload-form-label-infoform-description-tooltip' ) ] + } ); + this.categoriesField = new OO.ui.FieldLayout( this.categoriesWidget, { + label: mw.msg( 'upload-form-label-infoform-categories' ), + align: 'top' + } ); + this.dateField = new OO.ui.FieldLayout( this.dateWidget, { + label: mw.msg( 'upload-form-label-infoform-date' ), + align: 'top' + } ); + fieldset = new OO.ui.FieldsetLayout( { label: mw.msg( 'upload-form-label-infoform-title' ) } ); fieldset.addItems( [ - new OO.ui.FieldLayout( this.filenameWidget, { - label: mw.msg( 'upload-form-label-infoform-name' ), - align: 'top', - classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ], - notices: [ mw.msg( 'upload-form-label-infoform-name-tooltip' ) ] - } ), - new OO.ui.FieldLayout( this.descriptionWidget, { - label: mw.msg( 'upload-form-label-infoform-description' ), - align: 'top', - classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ], - notices: [ mw.msg( 'upload-form-label-infoform-description-tooltip' ) ] - } ), - new OO.ui.FieldLayout( this.categoriesWidget, { - label: mw.msg( 'upload-form-label-infoform-categories' ), - align: 'top' - } ), - new OO.ui.FieldLayout( this.dateWidget, { - label: mw.msg( 'upload-form-label-infoform-date' ), - align: 'top' - } ) + this.filenameField, + this.descriptionField, + this.categoriesField, + this.dateField ] ); this.infoForm = new OO.ui.FormLayout( { classes: [ 'mw-upload-bookletLayout-infoForm' ], @@ -256,12 +280,18 @@ * @inheritdoc */ mw.ForeignStructuredUpload.BookletLayout.prototype.onInfoFormChange = function () { - var layout = this; - $.when( - this.filenameWidget.getValidity(), - this.descriptionWidget.getValidity(), - this.dateWidget.getValidity() - ).done( function () { + var layout = this, + validityPromises = []; + + validityPromises.push( this.filenameWidget.getValidity() ); + if ( this.descriptionField.isVisible() ) { + validityPromises.push( this.descriptionWidget.getValidity() ); + } + if ( this.dateField.isVisible() ) { + validityPromises.push( this.dateWidget.getValidity() ); + } + + $.when.apply( $, validityPromises ).done( function () { layout.emit( 'infoValid', true ); } ).fail( function () { layout.emit( 'infoValid', false ); diff --git a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js index f90071ca4e..4a0366a0ae 100644 --- a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js +++ b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.js @@ -1,4 +1,4 @@ -( function ( mw, OO ) { +( function ( mw, $, OO ) { /** * @class mw.ForeignStructuredUpload * @extends mw.ForeignUpload @@ -21,11 +21,59 @@ this.descriptions = []; this.categories = []; + // Config for uploads to local wiki. + // Can be overridden with foreign wiki config when #loadConfig is called. + this.config = mw.config.get( 'wgUploadDialog' ); + mw.ForeignUpload.call( this, target, apiconfig ); } OO.inheritClass( ForeignStructuredUpload, mw.ForeignUpload ); + /** + * Get the configuration for the form and filepage from the foreign wiki, if any, and use it for + * this upload. + * + * @return {jQuery.Promise} Promise returning config object + */ + ForeignStructuredUpload.prototype.loadConfig = function () { + var deferred, + upload = this; + + if ( this.configPromise ) { + return this.configPromise; + } + + if ( this.target === 'local' ) { + deferred = $.Deferred(); + setTimeout( function () { + // Resolve asynchronously, so that it's harder to accidentally write synchronous code that + // will break for cross-wiki uploads + deferred.resolve( upload.config ); + } ); + this.configPromise = deferred.promise(); + } else { + this.configPromise = this.apiPromise.then( function ( api ) { + // Get the config from the foreign wiki + return api.get( { + action: 'query', + meta: 'siteinfo', + siprop: 'uploaddialog', + // For convenient true/false booleans + formatversion: 2 + } ).then( function ( resp ) { + // Foreign wiki might be running a pre-1.27 MediaWiki, without support for this + if ( resp.query && resp.query.uploaddialog ) { + upload.config = resp.query.uploaddialog; + } + return upload.config; + } ); + } ); + } + + return this.configPromise; + }; + /** * Add categories to the upload. * @@ -83,23 +131,23 @@ * @return {string} */ ForeignStructuredUpload.prototype.getText = function () { - return ( - '== {{int:filedesc}} ==\n' + - '{{Information' + - '\n|description=' + - this.getDescriptions() + - '\n|date=' + - this.getDate() + - '\n|source=' + - this.getSource() + - '\n|author=' + - this.getUser() + - '\n}}\n\n' + - '== {{int:license-header}} ==\n' + - this.getLicense() + - '\n\n' + - this.getCategories() - ); + return this.config.format.filepage + // Replace "numbered parameters" with the given information + .replace( '$DESCRIPTION', this.getDescriptions() ) + .replace( '$DATE', this.getDate() ) + .replace( '$SOURCE', this.getSource() ) + .replace( '$AUTHOR', this.getUser() ) + .replace( '$LICENSE', this.getLicense() ) + .replace( '$CATEGORIES', this.getCategories() ); + }; + + /** + * @inheritdoc + */ + ForeignStructuredUpload.prototype.getComment = function () { + return this.config.comment + .replace( '$PAGENAME', mw.config.get( 'wgPageName' ) ) + .replace( '$HOST', location.host ); }; /** @@ -128,7 +176,11 @@ for ( i = 0; i < this.descriptions.length; i++ ) { desc = this.descriptions[ i ]; - templateCalls.push( '{{' + desc.language + '|1=' + desc.text + '}}' ); + templateCalls.push( + this.config.format.description + .replace( '$LANGUAGE', desc.language ) + .replace( '$TEXT', desc.text ) + ); } return templateCalls.join( '\n' ); @@ -145,7 +197,7 @@ var i, cat, categoryLinks = []; if ( this.categories.length === 0 ) { - return '{{subst:unc}}'; + return this.config.format.uncategorized; } for ( i = 0; i < this.categories.length; i++ ) { @@ -163,9 +215,7 @@ * @return {string} */ ForeignStructuredUpload.prototype.getLicense = function () { - // Make sure this matches the messages for different targets in - // mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm - return this.target === 'shared' ? '{{self|cc-by-sa-4.0}}' : ''; + return this.config.format.license; }; /** @@ -175,7 +225,7 @@ * @return {string} */ ForeignStructuredUpload.prototype.getSource = function () { - return '{{own}}'; + return this.config.format.ownwork; }; /** @@ -200,4 +250,4 @@ }; mw.ForeignStructuredUpload = ForeignStructuredUpload; -}( mediaWiki, OO ) ); +}( mediaWiki, jQuery, OO ) ); diff --git a/resources/src/mediawiki/mediawiki.ForeignUpload.js b/resources/src/mediawiki/mediawiki.ForeignUpload.js index 1a0b59a160..eeeab68510 100644 --- a/resources/src/mediawiki/mediawiki.ForeignUpload.js +++ b/resources/src/mediawiki/mediawiki.ForeignUpload.js @@ -85,12 +85,6 @@ // actual API call methods to wait for the apiPromise to resolve // before continuing. mw.Upload.call( this, null ); - - if ( this.target !== 'local' ) { - // Keep these untranslated. We don't know the content language of the foreign wiki, best to - // stick to English in the text. - this.setComment( 'Cross-wiki upload from ' + location.host ); - } } OO.inheritClass( ForeignUpload, mw.Upload ); diff --git a/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js b/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js index 2b28cb4cf0..7a7469a267 100644 --- a/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js +++ b/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js @@ -176,18 +176,20 @@ return this.upload.getApi().then( function ( api ) { - // If the user can't upload anything, don't give them the option to. - return api.getUserInfo().then( - function ( userInfo ) { + return $.when( + booklet.upload.loadConfig(), + // If the user can't upload anything, don't give them the option to. + api.getUserInfo().then( function ( userInfo ) { if ( userInfo.rights.indexOf( 'upload' ) === -1 ) { // TODO Use a better error message when not all logged-in users can upload booklet.getPage( 'upload' ).$element.msg( 'api-error-mustbeloggedin' ); } return $.Deferred().resolve(); - }, - function () { - return $.Deferred().resolve(); - } + } ) + ).then( + null, + // Always resolve, never reject + function () { return $.Deferred().resolve(); } ); }, function ( errorMsg ) {