mw.ForeignStructuredUpload.BookletLayout: A/B test of 4 different interfaces
authorBartosz Dziewoński <matma.rex@gmail.com>
Fri, 11 Dec 2015 02:31:55 +0000 (03:31 +0100)
committerBartosz Dziewoński <matma.rex@gmail.com>
Tue, 15 Dec 2015 00:23:14 +0000 (00:23 +0000)
It turns out that people click the checkbox affirming that they are
they author of the file and that they release it under CC BY-SA even
when neither of those is true. So we're trying some interfaces that
require a modicum of thought rather than just a click on "I agree".

  Option 1: The form we have right now, with a single checkbox.

  Option 2: Four checkboxes, each with a label explaining one facet of
    the requirements (own work; no pictures of copyrighted work;
    educational/useful; irrevocably released as CC BY-SA).

  Option 3: Some Yes/No questions structured so that 'Yes' is not
    always the right answer to continue uploading.

  Option 4: Longer introduction before a single checkbox (as in option
    1), with examples of good and unacceptable content.

As only logged in users are able to upload files, we're able to bucket
them into four groups by user ID number. When the user completes a
file upload, the bucket number is saved server-side in a change tag by
the companion patch I90cb12c505b2581f36113ec6b4f7bf732f0971b7 (we could
match the user IDs cross-wiki by username, but that sounds painful).

For testing and debugging, add '?uploadbucket=N' to the URL to force
given interface option to appear. Any completed upload won't count
towards the bucket.

Note that for expediency, the tested options all assume uploads to
'shared' repository (that is, Wikimedia Commons). The winner's
messages will be tweaked to work with 'local' and other targets too.

This patch DOES NOT ENABLE THE TEST yet, it just implements the options.
Enabling it on specific wikis can be done via config:
* $wgForeignUploadTestEnabled = true/false (defaults to 'false')
  Whether the test is running.
* $wgForeignUploadTestDefault = 1/2/3/4 (defaults to '1')
  Interface to use when the test is not running (and for anons).

Bug: T120867
Bug: T121021
Change-Id: I557056b867c6a55ef2c9af321eb48893312632a3

18 files changed:
includes/DefaultSettings.php
includes/resourceloader/ResourceLoaderStartUpModule.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki/bookletlayout/option2/ccbysa.png [new file with mode: 0644]
resources/src/mediawiki/bookletlayout/option2/noderiv.png [new file with mode: 0644]
resources/src/mediawiki/bookletlayout/option2/ownwork.png [new file with mode: 0644]
resources/src/mediawiki/bookletlayout/option2/useful.png [new file with mode: 0644]
resources/src/mediawiki/bookletlayout/option4/camera.png [new file with mode: 0644]
resources/src/mediawiki/bookletlayout/option4/graphics.png [new file with mode: 0644]
resources/src/mediawiki/bookletlayout/option4/guide.html [new file with mode: 0644]
resources/src/mediawiki/bookletlayout/option4/search.png [new file with mode: 0644]
resources/src/mediawiki/bookletlayout/option4/website.png [new file with mode: 0644]
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.css
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js
resources/src/mediawiki/mediawiki.Upload.BookletLayout.js
resources/src/mediawiki/mediawiki.Upload.js

index 1642c09..39b133b 100644 (file)
@@ -535,6 +535,12 @@ $wgUseInstantCommons = false;
  */
 $wgForeignUploadTargets = array();
 
+/**
+ * Cross-wiki upload A/B test configuration.
+ */
+$wgForeignUploadTestEnabled = false;
+$wgForeignUploadTestDefault = 1;
+
 /**
  * File backend structure configuration.
  *
index 39e4e0b..fc128fb 100644 (file)
@@ -104,6 +104,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        'wgResourceLoaderLegacyModules' => self::getLegacyModules(),
                        'wgForeignUploadTargets' => $conf->get( 'ForeignUploadTargets' ),
                        'wgEnableUploads' => $conf->get( 'EnableUploads' ),
+                       'wgForeignUploadTestEnabled' => $conf->get( 'ForeignUploadTestEnabled' ),
+                       'wgForeignUploadTestDefault' => $conf->get( 'ForeignUploadTestDefault' ),
                );
 
                Hooks::run( 'ResourceLoaderGetConfigVars', array( &$vars ) );
index 62405c8..a1fff73 100644 (file)
        "foreign-structured-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].",
        "foreign-structured-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].",
        "foreign-structured-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.",
+       "foreign-structured-upload-form-2-label-intro": "Thank you for donating an image to be used on {{SITENAME}}. You should only continue if it meets several conditions:",
+       "foreign-structured-upload-form-2-label-ownwork": "It must be entirely <strong>your own creation</strong>, not just taken from the Internet",
+       "foreign-structured-upload-form-2-label-noderiv": "It has to contain <strong>no work by anyone else</strong>, or inspired by them",
+       "foreign-structured-upload-form-2-label-useful": "It should be <strong>educational and useful</strong> for teaching others",
+       "foreign-structured-upload-form-2-label-ccbysa": "It must be <strong>OK to publish forever</strong> on the Internet under the [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Attribution-ShareAlike 4.0] license",
+       "foreign-structured-upload-form-2-label-alternative": "If not all of the above are true, you may still be able to upload this file using the [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard], as long as it's available under a free license.",
+       "foreign-structured-upload-form-2-label-termsofuse": "By uploading the file, you attest that you own the copyright on this file, and agree to irrevocably release this file to Wikimedia Commons under the Creative Commons Attribution-ShareAlike 4.0 license, and you agree to the [https://wikimediafoundation.org/wiki/Terms_of_Use Terms of Use].",
+       "foreign-structured-upload-form-3-label-question-website": "Did you download this image from a website, or get it from an image search?",
+       "foreign-structured-upload-form-3-label-question-ownwork": "Did you create this image (take the photo, sketch the drawing, etc.) yourself?",
+       "foreign-structured-upload-form-3-label-question-noderiv": "Does it contain, or is it inspired by, work owned by anyone else, like a logo?",
+       "foreign-structured-upload-form-3-label-yes": "Yes",
+       "foreign-structured-upload-form-3-label-no": "No",
+       "foreign-structured-upload-form-3-label-alternative": "Unfortunately, in this case, this tool does not support uploading this file. You may still be able to upload it using the [https://commons.wikimedia.org/wiki/Special:UploadWizard Commons Upload Wizard], as long as it's available under a free license.",
+       "foreign-structured-upload-form-4-label-good": "Using this tool, you can upload educational graphics you've created and photographs you've taken, that don't contain work owned by someone else.",
+       "foreign-structured-upload-form-4-label-bad": "You can not upload images found on a search engine or downloaded from other websites.",
        "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.",
index 837c75f..e809e10 100644 (file)
        "upload-form-label-infoform-description": "Label for the file description input\n{{Identical|Description}}",
        "upload-form-label-usage-title": "Title for the insert form showing how to use the uploaded item.\n{{Identical|Usage}}",
        "upload-form-label-usage-filename": "Label for the file name input\n{{Identical|Filename}}",
-       "foreign-structured-upload-form-label-own-work": "Label for own work toggle",
+       "foreign-structured-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",
        "foreign-structured-upload-form-label-infoform-categories": "Label for category selector input\n{{Identical|Category}}",
        "foreign-structured-upload-form-label-infoform-date": "Label for date input\n{{Identical|Date}}",
        "foreign-structured-upload-form-label-own-work-message-local": "Message shown by local when a user affirms that they are allowed to upload a file to the local wiki.",
        "foreign-structured-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.",
        "foreign-structured-upload-form-label-not-own-work-message-default": "Message shown by default when a user cannot upload a file to a remote wiki.",
        "foreign-structured-upload-form-label-not-own-work-local-default": "Suggests uploading a file locally instead of to a remote wiki.",
-       "foreign-structured-upload-form-label-own-work-message-shared": "Legal message to show when the work is made by the uploader.",
-       "foreign-structured-upload-form-label-not-own-work-message-shared": "Message to show when the work isn't owned by the uploader.",
-       "foreign-structured-upload-form-label-not-own-work-local-shared": "Message suggesting the user might want to upload a file locally instead of to Wikimedia Commons. $1 is the name of the local wiki.",
+       "foreign-structured-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. Almost identical to {{msg-mw|foreign-structured-upload-form-2-label-termsofuse}}.",
+       "foreign-structured-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.",
+       "foreign-structured-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.",
+       "foreign-structured-upload-form-2-label-intro": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 2.png|thumb]] Introductory text in cross-wiki upload dialog.",
+       "foreign-structured-upload-form-2-label-ownwork": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 2.png|thumb]] Affirmative statement, used as checkbox label. The user must tick it to continue.",
+       "foreign-structured-upload-form-2-label-noderiv": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 2.png|thumb]] Affirmative statement, used as checkbox label. The user must tick it to continue.",
+       "foreign-structured-upload-form-2-label-useful": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 2.png|thumb]] Affirmative statement, used as checkbox label. The user must tick it to continue.",
+       "foreign-structured-upload-form-2-label-ccbysa": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 2.png|thumb]] Affirmative statement, used as checkbox label. The user must tick it to continue.",
+       "foreign-structured-upload-form-2-label-alternative": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 2.png|thumb]] Explains alternatives when the copyright isn't owned by the uploader.",
+       "foreign-structured-upload-form-2-label-termsofuse": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 2.png|thumb]] Legal message, confirming that the user is allowed to upload the file. Almost identical to {{msg-mw|foreign-structured-upload-form-label-own-work-message-shared}}.",
+       "foreign-structured-upload-form-3-label-question-website": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 3.png|thumb]] Question to the user, with Yes/No answer ({{msg-mw|foreign-structured-upload-form-3-label-yes}} / {{msg-mw|foreign-structured-upload-form-3-label-no}}). The answer determines whether the user will be able to continue.",
+       "foreign-structured-upload-form-3-label-question-ownwork": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 3.png|thumb]] Question to the user, with Yes/No answer ({{msg-mw|foreign-structured-upload-form-3-label-yes}} / {{msg-mw|foreign-structured-upload-form-3-label-no}}). The answer determines whether the user will be able to continue.",
+       "foreign-structured-upload-form-3-label-question-noderiv": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 3.png|thumb]] Question to the user, with Yes/No answer ({{msg-mw|foreign-structured-upload-form-3-label-yes}} / {{msg-mw|foreign-structured-upload-form-3-label-no}}). The answer determines whether the user will be able to continue.",
+       "foreign-structured-upload-form-3-label-yes": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 3.png|thumb]] {{Identical|Yes}}",
+       "foreign-structured-upload-form-3-label-no": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 3.png|thumb]] {{Identical|No}}",
+       "foreign-structured-upload-form-3-label-alternative": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 3.png|thumb]] Explains alternatives when the copyright isn't owned by the uploader.",
+       "foreign-structured-upload-form-4-label-good": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 4.png|thumb]] Gives examples of good content that is welcome. There is limited space for this text in the interface, please keep it short.",
+       "foreign-structured-upload-form-4-label-bad": "[[File:Cross-wiki media upload dialog, December 2015 AB test option 4.png|thumb]] Gives examples of bad content that is unacceptable. There is limited space for this text in the interface, please keep it short.",
        "backend-fail-stream": "Parameters:\n* $1 - a filename",
        "backend-fail-backup": "Parameters:\n* $1 - a filename",
        "backend-fail-notexists": "Parameters:\n* $1 - a filename",
index 3d5a563..02d87d4 100644 (file)
@@ -1267,6 +1267,24 @@ return array(
                        'foreign-structured-upload-form-label-own-work-message-local',
                        'foreign-structured-upload-form-label-not-own-work-message-local',
                        'foreign-structured-upload-form-label-not-own-work-local-local',
+                       'foreign-structured-upload-form-2-label-intro',
+                       'foreign-structured-upload-form-2-label-ownwork',
+                       'foreign-structured-upload-form-2-label-noderiv',
+                       'foreign-structured-upload-form-2-label-useful',
+                       'foreign-structured-upload-form-2-label-ccbysa',
+                       'foreign-structured-upload-form-2-label-alternative',
+                       'foreign-structured-upload-form-2-label-termsofuse',
+                       'foreign-structured-upload-form-3-label-question-website',
+                       'foreign-structured-upload-form-3-label-question-ownwork',
+                       'foreign-structured-upload-form-3-label-question-noderiv',
+                       'foreign-structured-upload-form-3-label-yes',
+                       'foreign-structured-upload-form-3-label-no',
+                       'foreign-structured-upload-form-3-label-alternative',
+                       'foreign-structured-upload-form-4-label-good',
+                       'foreign-structured-upload-form-4-label-bad',
+               ),
+               'templates' => array(
+                       'guide.html' => 'resources/src/mediawiki/bookletlayout/option4/guide.html',
                ),
        ),
        'mediawiki.toc' => array(
diff --git a/resources/src/mediawiki/bookletlayout/option2/ccbysa.png b/resources/src/mediawiki/bookletlayout/option2/ccbysa.png
new file mode 100644 (file)
index 0000000..507e62d
Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option2/ccbysa.png differ
diff --git a/resources/src/mediawiki/bookletlayout/option2/noderiv.png b/resources/src/mediawiki/bookletlayout/option2/noderiv.png
new file mode 100644 (file)
index 0000000..46766ca
Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option2/noderiv.png differ
diff --git a/resources/src/mediawiki/bookletlayout/option2/ownwork.png b/resources/src/mediawiki/bookletlayout/option2/ownwork.png
new file mode 100644 (file)
index 0000000..aabe59b
Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option2/ownwork.png differ
diff --git a/resources/src/mediawiki/bookletlayout/option2/useful.png b/resources/src/mediawiki/bookletlayout/option2/useful.png
new file mode 100644 (file)
index 0000000..1309eb3
Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option2/useful.png differ
diff --git a/resources/src/mediawiki/bookletlayout/option4/camera.png b/resources/src/mediawiki/bookletlayout/option4/camera.png
new file mode 100644 (file)
index 0000000..f52391f
Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/camera.png differ
diff --git a/resources/src/mediawiki/bookletlayout/option4/graphics.png b/resources/src/mediawiki/bookletlayout/option4/graphics.png
new file mode 100644 (file)
index 0000000..6fcc2c5
Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/graphics.png differ
diff --git a/resources/src/mediawiki/bookletlayout/option4/guide.html b/resources/src/mediawiki/bookletlayout/option4/guide.html
new file mode 100644 (file)
index 0000000..c747851
--- /dev/null
@@ -0,0 +1,12 @@
+<div class="mw-foreignStructuredUpload-bookletLayout-guide">
+       <div class="mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good">
+               <span></span>
+       </div>
+       <div class="mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad">
+               <span></span>
+       </div>
+       <div class="mw-foreignStructuredUpload-bookletLayout-guide-image mw-foreignStructuredUpload-bookletLayout-guide-image-camera"></div>
+       <div class="mw-foreignStructuredUpload-bookletLayout-guide-image mw-foreignStructuredUpload-bookletLayout-guide-image-graphics"></div>
+       <div class="mw-foreignStructuredUpload-bookletLayout-guide-image mw-foreignStructuredUpload-bookletLayout-guide-image-website"></div>
+       <div class="mw-foreignStructuredUpload-bookletLayout-guide-image mw-foreignStructuredUpload-bookletLayout-guide-image-search"></div>
+</div>
diff --git a/resources/src/mediawiki/bookletlayout/option4/search.png b/resources/src/mediawiki/bookletlayout/option4/search.png
new file mode 100644 (file)
index 0000000..5dca7fa
Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/search.png differ
diff --git a/resources/src/mediawiki/bookletlayout/option4/website.png b/resources/src/mediawiki/bookletlayout/option4/website.png
new file mode 100644 (file)
index 0000000..9c1914f
Binary files /dev/null and b/resources/src/mediawiki/bookletlayout/option4/website.png differ
index 4143520..c50ad7f 100644 (file)
@@ -1,5 +1,141 @@
+/* All */
+
 .mw-foreignStructuredUpload-bookletLayout-license {
        font-size: 90%;
        line-height: 1.4em;
        color: #555;
 }
+
+/* Option 2 */
+
+.mw-foreignStructuredUpload-bookletLayout-withicon.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
+       background-repeat: no-repeat;
+       background-size: 3.5em;
+       min-height: 4em;
+       margin-bottom: 0.25em;
+       display: table;
+       vertical-align: middle;
+       -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+       box-sizing: border-box;
+       width: 100%;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-withicon.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body {
+       /* Together with 'display: table' above, and FieldLayout styles, this aligns the text */
+       /* vertically to the middle. Don't try this at home, kids. */
+       display: table-row;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-ownwork {
+       /* @embed */
+       background-image: url(bookletlayout/option2/ownwork.png);
+       background-position: left center;
+       padding-left: 4.5em;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-noderiv {
+       /* @embed */
+       background-image: url(bookletlayout/option2/noderiv.png);
+       background-position: right center;
+       padding-right: 4.5em;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-useful {
+       /* @embed */
+       background-image: url(bookletlayout/option2/useful.png);
+       background-position: left center;
+       padding-left: 4.5em;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-ccbysa {
+       /* @embed */
+       background-image: url(bookletlayout/option2/ccbysa.png);
+       background-position: right center;
+       padding-right: 4.5em;
+}
+
+/* Option 3 */
+
+.mw-foreignStructuredUpload-bookletLayout-question .oo-ui-radioOptionWidget {
+       display: inline-block;
+       margin-right: 2em;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-checkbox.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+       /* No unreadable greys, please. This is the lightest WCAG AA compliant grey. */
+       color: #707070;
+}
+
+/* Option 4 */
+
+.mw-foreignStructuredUpload-bookletLayout-guide {
+       position: relative;
+       height: 315px;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good,
+.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad {
+       display: table;
+       width: 150px;
+       height: 140px;
+       position: absolute;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good span,
+.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad span {
+       display: table-cell;
+       vertical-align: middle;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good {
+       top: 0;
+       left: 0;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad {
+       bottom: 0;
+       right: 0;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-image {
+       position: absolute;
+       width: 200px;
+       height: 122px;
+       background-size: 200px;
+       background-repeat: no-repeat;
+       border: 1px solid #ccc;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-image-camera {
+       /* @embed */
+       background-image: url(bookletlayout/option4/camera.png);
+       top: 0;
+       right: 80px;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-image-graphics {
+       /* @embed */
+       background-image: url(bookletlayout/option4/graphics.png);
+       top: 50px;
+       right: 0;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-image-website {
+       /* @embed */
+       background-image: url(bookletlayout/option4/website.png);
+       left: 0;
+       bottom: 50px;
+}
+
+.mw-foreignStructuredUpload-bookletLayout-guide-image-search {
+       /* @embed */
+       background-image: url(bookletlayout/option4/search.png);
+       left: 80px;
+       bottom: 0;
+}
+
+.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline.mw-foreignStructuredUpload-bookletLayout-guide-checkbox {
+       /* We're really tight on vertical space. */
+       margin-bottom: 0;
+}
index 8595386..7ba73bf 100644 (file)
         * @inheritdoc
         */
        mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () {
+               var
+                       query = /[?&]uploadbucket=(\d)/.exec( location.search ),
+                       isTestEnabled = !!mw.config.get( 'wgForeignUploadTestEnabled' ),
+                       defaultBucket = mw.config.get( 'wgForeignUploadTestDefault' ) || 1,
+                       userId = mw.config.get( 'wgUserId' );
+
+               if ( query && query[ 1 ] ) {
+                       // Testing and debugging
+                       this.shouldRecordBucket = false;
+                       this.bucket = Number( query[ 1 ] );
+               } else if ( !userId || !isTestEnabled ) {
+                       // a) Anonymous user. This can actually happen, because our software sucks.
+                       // b) Test is not enabled on this wiki.
+                       // In either case, display the old interface and don't record bucket on uploads.
+                       this.shouldRecordBucket = false;
+                       this.bucket = defaultBucket;
+               } else {
+                       // Regular logged in user on a wiki where the test is running
+                       this.shouldRecordBucket = true;
+                       this.bucket = ( userId % 4 ) + 1; // 1, 2, 3, 4
+               }
+
+               return this[ 'renderUploadForm' + this.bucket ]();
+       };
+
+       /**
+        * Test option 1, the original one. See T120867.
+        */
+       mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm1 = function () {
                var fieldset, $ownWorkMessage, $notOwnWorkMessage,
+                       onUploadFormChange,
                        ownWorkMessage, notOwnWorkMessage, notOwnWorkLocal,
                        validTargets = mw.config.get( 'wgForeignUploadTargets' ),
                        target = this.target || validTargets[ 0 ] || 'local',
                        layout = this;
 
+               // Temporary override to make my life easier during A/B test
+               target = 'shared';
+
                // foreign-structured-upload-form-label-own-work-message-local
                // foreign-structured-upload-form-label-own-work-message-shared
                ownWorkMessage = mw.message( 'foreign-structured-upload-form-label-own-work-message-' + target );
                        $( '<p>' ).html( notOwnWorkMessage.parse() ),
                        $( '<p>' ).html( notOwnWorkLocal.parse() )
                );
-               $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' ).attr( 'target', '_blank' );
+               $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' )
+                       .attr( 'target', '_blank' )
+                       .on( 'click', function ( e ) {
+                               // Some stupid code is trying to prevent default on all clicks, which causes the links to
+                               // not be openable, don't let it
+                               e.stopPropagation();
+                       } );
 
                this.selectFileWidget = new OO.ui.SelectFileWidget();
                this.messageLabel = new OO.ui.LabelWidget( {
                ] );
                this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
 
+               onUploadFormChange = function () {
+                       var file = this.selectFileWidget.getValue(),
+                               ownWork = this.ownWorkCheckbox.isSelected(),
+                               valid = !!file && ownWork;
+                       this.emit( 'uploadValid', valid );
+               };
+
                // Validation
-               this.selectFileWidget.on( 'change', this.onUploadFormChange.bind( this ) );
-               this.ownWorkCheckbox.on( 'change', this.onUploadFormChange.bind( this ) );
+               this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
+               this.ownWorkCheckbox.on( 'change', onUploadFormChange.bind( this ) );
 
                return this.uploadForm;
        };
 
        /**
-        * @inheritdoc
+        * Test option 2, idea A from T121021. See T120867.
+        */
+       mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm2 = function () {
+               var fieldset, checkboxes, fields, onUploadFormChange;
+
+               this.selectFileWidget = new OO.ui.SelectFileWidget();
+               this.licenseCheckboxes = checkboxes = [
+                       new OO.ui.CheckboxInputWidget(),
+                       new OO.ui.CheckboxInputWidget(),
+                       new OO.ui.CheckboxInputWidget(),
+                       new OO.ui.CheckboxInputWidget()
+               ];
+
+               fields = [
+                       new OO.ui.FieldLayout( this.selectFileWidget, {
+                               align: 'top',
+                               label: mw.msg( 'upload-form-label-select-file' )
+                       } ),
+                       new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
+                               label: mw.message( 'foreign-structured-upload-form-2-label-intro' ).parseDom()
+                       } ), {
+                               align: 'top'
+                       } ),
+                       new OO.ui.FieldLayout( checkboxes[ 0 ], {
+                               align: 'inline',
+                               classes: [
+                                       'mw-foreignStructuredUpload-bookletLayout-withicon',
+                                       'mw-foreignStructuredUpload-bookletLayout-ownwork'
+                               ],
+                               label: mw.message( 'foreign-structured-upload-form-2-label-ownwork' ).parseDom()
+                       } ),
+                       new OO.ui.FieldLayout( checkboxes[ 1 ], {
+                               align: 'inline',
+                               classes: [
+                                       'mw-foreignStructuredUpload-bookletLayout-withicon',
+                                       'mw-foreignStructuredUpload-bookletLayout-noderiv'
+                               ],
+                               label: mw.message( 'foreign-structured-upload-form-2-label-noderiv' ).parseDom()
+                       } ),
+                       new OO.ui.FieldLayout( checkboxes[ 2 ], {
+                               align: 'inline',
+                               classes: [
+                                       'mw-foreignStructuredUpload-bookletLayout-withicon',
+                                       'mw-foreignStructuredUpload-bookletLayout-useful'
+                               ],
+                               label: mw.message( 'foreign-structured-upload-form-2-label-useful' ).parseDom()
+                       } ),
+                       new OO.ui.FieldLayout( checkboxes[ 3 ], {
+                               align: 'inline',
+                               classes: [
+                                       'mw-foreignStructuredUpload-bookletLayout-withicon',
+                                       'mw-foreignStructuredUpload-bookletLayout-ccbysa'
+                               ],
+                               label: mw.message( 'foreign-structured-upload-form-2-label-ccbysa' ).parseDom()
+                       } ),
+                       new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
+                               label: $()
+                                       .add( $( '<p>' ).msg( 'foreign-structured-upload-form-2-label-alternative' ) )
+                                       .add( $( '<p>' ).msg( 'foreign-structured-upload-form-2-label-termsofuse' )
+                                               .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' ) )
+                       } ), {
+                               align: 'top'
+                       } )
+               ];
+
+               fieldset = new OO.ui.FieldsetLayout( { items: fields } );
+               this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
+
+               this.uploadForm.$element.find( 'a' )
+                       .attr( 'target', '_blank' )
+                       .on( 'click', function ( e ) {
+                               // Some stupid code is trying to prevent default on all clicks, which causes the links to
+                               // not be openable, don't let it
+                               e.stopPropagation();
+                       } );
+
+               onUploadFormChange = function () {
+                       var file = this.selectFileWidget.getValue(),
+                               checks = checkboxes.every( function ( checkbox ) {
+                                       return checkbox.isSelected();
+                               } ),
+                               valid = !!file && checks;
+                       this.emit( 'uploadValid', valid );
+               };
+
+               // Validation
+               this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
+               checkboxes[ 0 ].on( 'change', onUploadFormChange.bind( this ) );
+               checkboxes[ 1 ].on( 'change', onUploadFormChange.bind( this ) );
+               checkboxes[ 2 ].on( 'change', onUploadFormChange.bind( this ) );
+               checkboxes[ 3 ].on( 'change', onUploadFormChange.bind( this ) );
+
+               return this.uploadForm;
+       };
+
+       /**
+        * Test option 3, idea D from T121021. See T120867.
+        */
+       mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm3 = function () {
+               var ownWorkCheckbox, fieldset, yesMsg, noMsg, selects, selectFields,
+                       alternativeField, fields, onUploadFormChange;
+
+               this.selectFileWidget = new OO.ui.SelectFileWidget();
+               this.ownWorkCheckbox = ownWorkCheckbox = new OO.ui.CheckboxInputWidget();
+
+               yesMsg = mw.message( 'foreign-structured-upload-form-3-label-yes' ).text();
+               noMsg = mw.message( 'foreign-structured-upload-form-3-label-no' ).text();
+               selects = [
+                       new OO.ui.RadioSelectWidget( {
+                               items: [
+                                       new OO.ui.RadioOptionWidget( { data: false, label: yesMsg } ),
+                                       new OO.ui.RadioOptionWidget( { data: true, label: noMsg } )
+                               ]
+                       } ),
+                       new OO.ui.RadioSelectWidget( {
+                               items: [
+                                       new OO.ui.RadioOptionWidget( { data: true, label: yesMsg } ),
+                                       new OO.ui.RadioOptionWidget( { data: false, label: noMsg } )
+                               ]
+                       } ),
+                       new OO.ui.RadioSelectWidget( {
+                               items: [
+                                       new OO.ui.RadioOptionWidget( { data: false, label: yesMsg } ),
+                                       new OO.ui.RadioOptionWidget( { data: true, label: noMsg } )
+                               ]
+                       } )
+               ];
+
+               this.licenseSelectFields = selectFields = [
+                       new OO.ui.FieldLayout( selects[ 0 ], {
+                               align: 'top',
+                               classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
+                               label: mw.message( 'foreign-structured-upload-form-3-label-question-website' ).parseDom()
+                       } ),
+                       new OO.ui.FieldLayout( selects[ 1 ], {
+                               align: 'top',
+                               classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
+                               label: mw.message( 'foreign-structured-upload-form-3-label-question-ownwork' ).parseDom()
+                       } ).toggle( false ),
+                       new OO.ui.FieldLayout( selects[ 2 ], {
+                               align: 'top',
+                               classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
+                               label: mw.message( 'foreign-structured-upload-form-3-label-question-noderiv' ).parseDom()
+                       } ).toggle( false )
+               ];
+
+               alternativeField = new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
+                       label: mw.message( 'foreign-structured-upload-form-3-label-alternative' ).parseDom()
+               } ), {
+                       align: 'top'
+               } ).toggle( false );
+
+               // Choosing the right answer to each question shows the next question.
+               // Switching to wrong answer hides all subsequent questions.
+               selects.forEach( function ( select, i ) {
+                       select.on( 'choose', function ( selectedOption ) {
+                               var isRightAnswer = !!selectedOption.getData();
+                               alternativeField.toggle( !isRightAnswer );
+                               if ( i + 1 === selectFields.length ) {
+                                       // Last question
+                                       return;
+                               }
+                               if ( isRightAnswer ) {
+                                       selectFields[ i + 1 ].toggle( true );
+                               } else {
+                                       selectFields.slice( i + 1 ).forEach( function ( field ) {
+                                               field.fieldWidget.selectItem( null );
+                                               field.toggle( false );
+                                       } );
+                               }
+                       } );
+               } );
+
+               fields = [
+                       new OO.ui.FieldLayout( this.selectFileWidget, {
+                               align: 'top',
+                               label: mw.msg( 'upload-form-label-select-file' )
+                       } ),
+                       selectFields[ 0 ],
+                       selectFields[ 1 ],
+                       selectFields[ 2 ],
+                       alternativeField,
+                       new OO.ui.FieldLayout( ownWorkCheckbox, {
+                               classes: [ 'mw-foreignStructuredUpload-bookletLayout-checkbox' ],
+                               align: 'inline',
+                               label: mw.message( 'foreign-structured-upload-form-label-own-work-message-shared' ).parseDom()
+                       } )
+               ];
+
+               // Must be done late, after it's been associated with the FieldLayout
+               ownWorkCheckbox.setDisabled( true );
+
+               fieldset = new OO.ui.FieldsetLayout( { items: fields } );
+               this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
+
+               this.uploadForm.$element.find( 'a' )
+                       .attr( 'target', '_blank' )
+                       .on( 'click', function ( e ) {
+                               // Some stupid code is trying to prevent default on all clicks, which causes the links to
+                               // not be openable, don't let it
+                               e.stopPropagation();
+                       } );
+
+               onUploadFormChange = function () {
+                       var file = this.selectFileWidget.getValue(),
+                               checkbox = ownWorkCheckbox.isSelected(),
+                               rightAnswers = selects.every( function ( select ) {
+                                       return select.getSelectedItem() && !!select.getSelectedItem().getData();
+                               } ),
+                               valid = !!file && checkbox && rightAnswers;
+                       ownWorkCheckbox.setDisabled( !rightAnswers );
+                       if ( !rightAnswers ) {
+                               ownWorkCheckbox.setSelected( false );
+                       }
+                       this.emit( 'uploadValid', valid );
+               };
+
+               // Validation
+               this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
+               this.ownWorkCheckbox.on( 'change', onUploadFormChange.bind( this ) );
+               selects[ 0 ].on( 'choose', onUploadFormChange.bind( this ) );
+               selects[ 1 ].on( 'choose', onUploadFormChange.bind( this ) );
+               selects[ 2 ].on( 'choose', onUploadFormChange.bind( this ) );
+
+               return this.uploadForm;
+       };
+
+       /**
+        * Test option 4, idea E from T121021. See T120867.
         */
-       mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () {
-               var file = this.selectFileWidget.getValue(),
-                       ownWork = this.ownWorkCheckbox.isSelected(),
-                       valid = !!file && ownWork;
-               this.emit( 'uploadValid', valid );
+       mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm4 = function () {
+               var fieldset, $guide;
+               this.renderUploadForm1();
+               fieldset = this.uploadForm.getItems()[ 0 ];
+
+               $guide = mw.template.get( 'mediawiki.ForeignStructuredUpload.BookletLayout', 'guide.html' ).render();
+               $guide.find( '.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good span' )
+                       .msg( 'foreign-structured-upload-form-4-label-good' );
+               $guide.find( '.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad span' )
+                       .msg( 'foreign-structured-upload-form-4-label-bad' );
+
+               // Note the index, we insert after the SelectFileWidget field
+               fieldset.addItems( [
+                       new OO.ui.FieldLayout( new OO.ui.Widget( {
+                               $content: $guide
+                       } ), {
+                               align: 'top'
+                       } )
+               ], 1 );
+
+               // Hook for custom styles
+               fieldset.getItems()[ 2 ].$element.addClass( 'mw-foreignStructuredUpload-bookletLayout-guide-checkbox' );
+
+               // Streamline: remove mention of local Special:Upload
+               fieldset.getItems()[ 3 ].$element.find( 'p' ).last().remove();
+
+               return this.uploadForm;
        };
 
+       /**
+        * @inheritdoc
+        */
+       mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () {};
+
        /**
         * @inheritdoc
         */
        mw.ForeignStructuredUpload.BookletLayout.prototype.clear = function () {
                mw.ForeignStructuredUpload.BookletLayout.parent.prototype.clear.call( this );
 
-               this.ownWorkCheckbox.setSelected( false );
+               if ( this.ownWorkCheckbox ) {
+                       this.ownWorkCheckbox.setSelected( false );
+               }
+               if ( this.licenseCheckboxes ) {
+                       this.licenseCheckboxes.forEach( function ( checkbox ) {
+                               checkbox.setSelected( false );
+                       } );
+               }
+               if ( this.licenseSelectFields ) {
+                       this.licenseSelectFields.forEach( function ( field, i ) {
+                               field.fieldWidget.selectItem( null );
+                               if ( i !== 0 ) {
+                                       field.toggle( false );
+                               }
+                       } );
+               }
+
                this.categoriesWidget.setItemsFromData( [] );
                this.dateWidget.setValue( '' ).setValidityFlag( true );
        };
index 7401445..47f4388 100644 (file)
                this.filenameWidget.setValue( file.name );
                this.setPage( 'info' );
 
+               if ( this.shouldRecordBucket ) {
+                       this.upload.bucket = this.bucket;
+               }
+
                this.upload.setFile( file );
                // Explicitly set the filename so that the old filename isn't used in case of retry
                this.upload.setFilenameFromFile();
index 4f8789d..d80b4eb 100644 (file)
                        upload.setState( Upload.State.UPLOADING );
 
                        return finishStash( {
+                               bucket: upload.bucket, // Automatically ignored if undefined
                                watchlist: ( upload.getWatchlist() ) ? 1 : undefined,
                                comment: upload.getComment(),
                                filename: upload.getFilename(),