Merge "Import: Handle uploads with sha1 starting with 0 properly"
[mediawiki.git] / resources / src / mediawiki / mediawiki.ForeignStructuredUpload.BookletLayout.js
blobddf7f2b37e545187e7f0d73ba2eba2fea57b0599
1 /*global moment */
2 ( function ( $, mw ) {
4         /**
5          * mw.ForeignStructuredUpload.BookletLayout encapsulates the process
6          * of uploading a file to MediaWiki using the mw.ForeignStructuredUpload model.
7          *
8          *     var uploadDialog = new mw.Upload.Dialog( {
9          *         bookletClass: mw.ForeignStructuredUpload.BookletLayout,
10          *         booklet: {
11          *             target: 'local'
12          *         }
13          *     } );
14          *     var windowManager = new OO.ui.WindowManager();
15          *     $( 'body' ).append( windowManager.$element );
16          *     windowManager.addWindows( [ uploadDialog ] );
17          *
18          * @class mw.ForeignStructuredUpload.BookletLayout
19          * @uses mw.ForeignStructuredUpload
20          * @extends mw.Upload.BookletLayout
21          * @cfg {string} [target] Used to choose the target repository.
22          *     If nothing is passed, the {@link mw.ForeignUpload#property-target default} is used.
23          */
24         mw.ForeignStructuredUpload.BookletLayout = function ( config ) {
25                 config = config || {};
26                 // Parent constructor
27                 mw.ForeignStructuredUpload.BookletLayout.parent.call( this, config );
29                 this.target = config.target;
30         };
32         /* Setup */
34         OO.inheritClass( mw.ForeignStructuredUpload.BookletLayout, mw.Upload.BookletLayout );
36         /* Uploading */
38         /**
39          * @inheritdoc
40          */
41         mw.ForeignStructuredUpload.BookletLayout.prototype.initialize = function () {
42                 var deferred = $.Deferred();
43                 mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this )
44                         .done( function () {
45                                 // Point the CategorySelector to the right wiki
46                                 this.upload.apiPromise.done( function ( api ) {
47                                         // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything
48                                         if ( api.apiUrl ) {
49                                                 // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance
50                                                 this.categoriesWidget.api = new mw.ForeignApi( api.apiUrl );
51                                         }
52                                         deferred.resolve();
53                                 }.bind( this ) );
54                         }.bind( this ) );
55                 return deferred.promise();
56         };
58         /**
59          * Returns a {@link mw.ForeignStructuredUpload mw.ForeignStructuredUpload}
60          * with the {@link #cfg-target target} specified in config.
61          *
62          * @protected
63          * @return {mw.Upload}
64          */
65         mw.ForeignStructuredUpload.BookletLayout.prototype.createUpload = function () {
66                 return new mw.ForeignStructuredUpload( this.target );
67         };
69         /* Form renderers */
71         /**
72          * @inheritdoc
73          */
74         mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () {
75                 var
76                         query = /[?&]uploadbucket=(\d)/.exec( location.search ),
77                         isTestEnabled = !!mw.config.get( 'wgForeignUploadTestEnabled' ),
78                         defaultBucket = mw.config.get( 'wgForeignUploadTestDefault' ) || 1,
79                         userId = mw.config.get( 'wgUserId' );
81                 if ( query && query[ 1 ] ) {
82                         // Testing and debugging
83                         this.shouldRecordBucket = false;
84                         this.bucket = Number( query[ 1 ] );
85                 } else if ( !userId || !isTestEnabled ) {
86                         // a) Anonymous user. This can actually happen, because our software sucks.
87                         // b) Test is not enabled on this wiki.
88                         // In either case, display the old interface and don't record bucket on uploads.
89                         this.shouldRecordBucket = false;
90                         this.bucket = defaultBucket;
91                 } else {
92                         // Regular logged in user on a wiki where the test is running
93                         this.shouldRecordBucket = true;
94                         this.bucket = ( userId % 4 ) + 1; // 1, 2, 3, 4
95                 }
97                 return this[ 'renderUploadForm' + this.bucket ]();
98         };
100         /**
101          * Test option 1, the original one. See T120867.
102          */
103         mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm1 = function () {
104                 var fieldset, $ownWorkMessage, $notOwnWorkMessage,
105                         onUploadFormChange,
106                         ownWorkMessage, notOwnWorkMessage, notOwnWorkLocal,
107                         validTargets = mw.config.get( 'wgForeignUploadTargets' ),
108                         target = this.target || validTargets[ 0 ] || 'local',
109                         layout = this;
111                 // Temporary override to make my life easier during A/B test
112                 target = 'shared';
114                 // foreign-structured-upload-form-label-own-work-message-local
115                 // foreign-structured-upload-form-label-own-work-message-shared
116                 ownWorkMessage = mw.message( 'foreign-structured-upload-form-label-own-work-message-' + target );
117                 // foreign-structured-upload-form-label-not-own-work-message-local
118                 // foreign-structured-upload-form-label-not-own-work-message-shared
119                 notOwnWorkMessage = mw.message( 'foreign-structured-upload-form-label-not-own-work-message-' + target );
120                 // foreign-structured-upload-form-label-not-own-work-local-local
121                 // foreign-structured-upload-form-label-not-own-work-local-shared
122                 notOwnWorkLocal = mw.message( 'foreign-structured-upload-form-label-not-own-work-local-' + target );
124                 if ( !ownWorkMessage.exists() ) {
125                         ownWorkMessage = mw.message( 'foreign-structured-upload-form-label-own-work-message-default' );
126                 }
127                 if ( !notOwnWorkMessage.exists() ) {
128                         notOwnWorkMessage = mw.message( 'foreign-structured-upload-form-label-not-own-work-message-default' );
129                 }
130                 if ( !notOwnWorkLocal.exists() ) {
131                         notOwnWorkLocal = mw.message( 'foreign-structured-upload-form-label-not-own-work-local-default' );
132                 }
134                 $ownWorkMessage = $( '<p>' ).html( ownWorkMessage.parse() )
135                         .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' );
136                 $notOwnWorkMessage = $( '<div>' ).append(
137                         $( '<p>' ).html( notOwnWorkMessage.parse() ),
138                         $( '<p>' ).html( notOwnWorkLocal.parse() )
139                 );
140                 $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' )
141                         .attr( 'target', '_blank' )
142                         .on( 'click', function ( e ) {
143                                 // Some stupid code is trying to prevent default on all clicks, which causes the links to
144                                 // not be openable, don't let it
145                                 e.stopPropagation();
146                         } );
148                 this.selectFileWidget = new OO.ui.SelectFileWidget();
149                 this.messageLabel = new OO.ui.LabelWidget( {
150                         label: $notOwnWorkMessage
151                 } );
152                 this.ownWorkCheckbox = new OO.ui.CheckboxInputWidget().on( 'change', function ( on ) {
153                         layout.messageLabel.toggle( !on );
154                 } );
156                 fieldset = new OO.ui.FieldsetLayout();
157                 fieldset.addItems( [
158                         new OO.ui.FieldLayout( this.selectFileWidget, {
159                                 align: 'top',
160                                 label: mw.msg( 'upload-form-label-select-file' )
161                         } ),
162                         new OO.ui.FieldLayout( this.ownWorkCheckbox, {
163                                 align: 'inline',
164                                 label: $( '<div>' ).append(
165                                         $( '<p>' ).text( mw.msg( 'foreign-structured-upload-form-label-own-work' ) ),
166                                         $ownWorkMessage
167                                 )
168                         } ),
169                         new OO.ui.FieldLayout( this.messageLabel, {
170                                 align: 'top'
171                         } )
172                 ] );
173                 this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
175                 onUploadFormChange = function () {
176                         var file = this.selectFileWidget.getValue(),
177                                 ownWork = this.ownWorkCheckbox.isSelected(),
178                                 valid = !!file && ownWork;
179                         this.emit( 'uploadValid', valid );
180                 };
182                 // Validation
183                 this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
184                 this.ownWorkCheckbox.on( 'change', onUploadFormChange.bind( this ) );
186                 this.selectFileWidget.on( 'change', function () {
187                         var file = layout.getFile();
189                         // Set the date to lastModified once we have the file
190                         if ( layout.getDateFromLastModified( file ) !== undefined ) {
191                                 layout.dateWidget.setValue( layout.getDateFromLastModified( file ) );
192                         }
194                         // Check if we have EXIF data and set to that where available
195                         layout.getDateFromExif( file ).done( function ( date ) {
196                                 layout.dateWidget.setValue( date );
197                         } );
198                 } );
200                 return this.uploadForm;
201         };
203         /**
204          * Test option 2, idea A from T121021. See T120867.
205          */
206         mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm2 = function () {
207                 var fieldset, checkboxes, fields, onUploadFormChange;
209                 this.selectFileWidget = new OO.ui.SelectFileWidget();
210                 this.licenseCheckboxes = checkboxes = [
211                         new OO.ui.CheckboxInputWidget(),
212                         new OO.ui.CheckboxInputWidget(),
213                         new OO.ui.CheckboxInputWidget(),
214                         new OO.ui.CheckboxInputWidget()
215                 ];
217                 fields = [
218                         new OO.ui.FieldLayout( this.selectFileWidget, {
219                                 align: 'top',
220                                 label: mw.msg( 'upload-form-label-select-file' )
221                         } ),
222                         new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
223                                 label: mw.message( 'foreign-structured-upload-form-2-label-intro' ).parseDom()
224                         } ), {
225                                 align: 'top'
226                         } ),
227                         new OO.ui.FieldLayout( checkboxes[ 0 ], {
228                                 align: 'inline',
229                                 classes: [
230                                         'mw-foreignStructuredUpload-bookletLayout-withicon',
231                                         'mw-foreignStructuredUpload-bookletLayout-ownwork'
232                                 ],
233                                 label: mw.message( 'foreign-structured-upload-form-2-label-ownwork' ).parseDom()
234                         } ),
235                         new OO.ui.FieldLayout( checkboxes[ 1 ], {
236                                 align: 'inline',
237                                 classes: [
238                                         'mw-foreignStructuredUpload-bookletLayout-withicon',
239                                         'mw-foreignStructuredUpload-bookletLayout-noderiv'
240                                 ],
241                                 label: mw.message( 'foreign-structured-upload-form-2-label-noderiv' ).parseDom()
242                         } ),
243                         new OO.ui.FieldLayout( checkboxes[ 2 ], {
244                                 align: 'inline',
245                                 classes: [
246                                         'mw-foreignStructuredUpload-bookletLayout-withicon',
247                                         'mw-foreignStructuredUpload-bookletLayout-useful'
248                                 ],
249                                 label: mw.message( 'foreign-structured-upload-form-2-label-useful' ).parseDom()
250                         } ),
251                         new OO.ui.FieldLayout( checkboxes[ 3 ], {
252                                 align: 'inline',
253                                 classes: [
254                                         'mw-foreignStructuredUpload-bookletLayout-withicon',
255                                         'mw-foreignStructuredUpload-bookletLayout-ccbysa'
256                                 ],
257                                 label: mw.message( 'foreign-structured-upload-form-2-label-ccbysa' ).parseDom()
258                         } ),
259                         new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
260                                 label: $()
261                                         .add( $( '<p>' ).msg( 'foreign-structured-upload-form-2-label-alternative' ) )
262                                         .add( $( '<p>' ).msg( 'foreign-structured-upload-form-2-label-termsofuse' )
263                                                 .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' ) )
264                         } ), {
265                                 align: 'top'
266                         } )
267                 ];
269                 fieldset = new OO.ui.FieldsetLayout( { items: fields } );
270                 this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
272                 this.uploadForm.$element.find( 'a' )
273                         .attr( 'target', '_blank' )
274                         .on( 'click', function ( e ) {
275                                 // Some stupid code is trying to prevent default on all clicks, which causes the links to
276                                 // not be openable, don't let it
277                                 e.stopPropagation();
278                         } );
280                 onUploadFormChange = function () {
281                         var file = this.selectFileWidget.getValue(),
282                                 checks = checkboxes.every( function ( checkbox ) {
283                                         return checkbox.isSelected();
284                                 } ),
285                                 valid = !!file && checks;
286                         this.emit( 'uploadValid', valid );
287                 };
289                 // Validation
290                 this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
291                 checkboxes[ 0 ].on( 'change', onUploadFormChange.bind( this ) );
292                 checkboxes[ 1 ].on( 'change', onUploadFormChange.bind( this ) );
293                 checkboxes[ 2 ].on( 'change', onUploadFormChange.bind( this ) );
294                 checkboxes[ 3 ].on( 'change', onUploadFormChange.bind( this ) );
296                 return this.uploadForm;
297         };
299         /**
300          * Test option 3, idea D from T121021. See T120867.
301          */
302         mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm3 = function () {
303                 var ownWorkCheckbox, fieldset, yesMsg, noMsg, selects, selectFields,
304                         alternativeField, fields, onUploadFormChange;
306                 this.selectFileWidget = new OO.ui.SelectFileWidget();
307                 this.ownWorkCheckbox = ownWorkCheckbox = new OO.ui.CheckboxInputWidget();
309                 yesMsg = mw.message( 'foreign-structured-upload-form-3-label-yes' ).text();
310                 noMsg = mw.message( 'foreign-structured-upload-form-3-label-no' ).text();
311                 selects = [
312                         new OO.ui.RadioSelectWidget( {
313                                 items: [
314                                         new OO.ui.RadioOptionWidget( { data: false, label: yesMsg } ),
315                                         new OO.ui.RadioOptionWidget( { data: true, label: noMsg } )
316                                 ]
317                         } ),
318                         new OO.ui.RadioSelectWidget( {
319                                 items: [
320                                         new OO.ui.RadioOptionWidget( { data: true, label: yesMsg } ),
321                                         new OO.ui.RadioOptionWidget( { data: false, label: noMsg } )
322                                 ]
323                         } ),
324                         new OO.ui.RadioSelectWidget( {
325                                 items: [
326                                         new OO.ui.RadioOptionWidget( { data: false, label: yesMsg } ),
327                                         new OO.ui.RadioOptionWidget( { data: true, label: noMsg } )
328                                 ]
329                         } )
330                 ];
332                 this.licenseSelectFields = selectFields = [
333                         new OO.ui.FieldLayout( selects[ 0 ], {
334                                 align: 'top',
335                                 classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
336                                 label: mw.message( 'foreign-structured-upload-form-3-label-question-website' ).parseDom()
337                         } ),
338                         new OO.ui.FieldLayout( selects[ 1 ], {
339                                 align: 'top',
340                                 classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
341                                 label: mw.message( 'foreign-structured-upload-form-3-label-question-ownwork' ).parseDom()
342                         } ).toggle( false ),
343                         new OO.ui.FieldLayout( selects[ 2 ], {
344                                 align: 'top',
345                                 classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
346                                 label: mw.message( 'foreign-structured-upload-form-3-label-question-noderiv' ).parseDom()
347                         } ).toggle( false )
348                 ];
350                 alternativeField = new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
351                         label: mw.message( 'foreign-structured-upload-form-3-label-alternative' ).parseDom()
352                 } ), {
353                         align: 'top'
354                 } ).toggle( false );
356                 // Choosing the right answer to each question shows the next question.
357                 // Switching to wrong answer hides all subsequent questions.
358                 selects.forEach( function ( select, i ) {
359                         select.on( 'choose', function ( selectedOption ) {
360                                 var isRightAnswer = !!selectedOption.getData();
361                                 alternativeField.toggle( !isRightAnswer );
362                                 if ( i + 1 === selectFields.length ) {
363                                         // Last question
364                                         return;
365                                 }
366                                 if ( isRightAnswer ) {
367                                         selectFields[ i + 1 ].toggle( true );
368                                 } else {
369                                         selectFields.slice( i + 1 ).forEach( function ( field ) {
370                                                 field.fieldWidget.selectItem( null );
371                                                 field.toggle( false );
372                                         } );
373                                 }
374                         } );
375                 } );
377                 fields = [
378                         new OO.ui.FieldLayout( this.selectFileWidget, {
379                                 align: 'top',
380                                 label: mw.msg( 'upload-form-label-select-file' )
381                         } ),
382                         selectFields[ 0 ],
383                         selectFields[ 1 ],
384                         selectFields[ 2 ],
385                         alternativeField,
386                         new OO.ui.FieldLayout( ownWorkCheckbox, {
387                                 classes: [ 'mw-foreignStructuredUpload-bookletLayout-checkbox' ],
388                                 align: 'inline',
389                                 label: mw.message( 'foreign-structured-upload-form-label-own-work-message-shared' ).parseDom()
390                         } )
391                 ];
393                 // Must be done late, after it's been associated with the FieldLayout
394                 ownWorkCheckbox.setDisabled( true );
396                 fieldset = new OO.ui.FieldsetLayout( { items: fields } );
397                 this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
399                 this.uploadForm.$element.find( 'a' )
400                         .attr( 'target', '_blank' )
401                         .on( 'click', function ( e ) {
402                                 // Some stupid code is trying to prevent default on all clicks, which causes the links to
403                                 // not be openable, don't let it
404                                 e.stopPropagation();
405                         } );
407                 onUploadFormChange = function () {
408                         var file = this.selectFileWidget.getValue(),
409                                 checkbox = ownWorkCheckbox.isSelected(),
410                                 rightAnswers = selects.every( function ( select ) {
411                                         return select.getSelectedItem() && !!select.getSelectedItem().getData();
412                                 } ),
413                                 valid = !!file && checkbox && rightAnswers;
414                         ownWorkCheckbox.setDisabled( !rightAnswers );
415                         if ( !rightAnswers ) {
416                                 ownWorkCheckbox.setSelected( false );
417                         }
418                         this.emit( 'uploadValid', valid );
419                 };
421                 // Validation
422                 this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
423                 this.ownWorkCheckbox.on( 'change', onUploadFormChange.bind( this ) );
424                 selects[ 0 ].on( 'choose', onUploadFormChange.bind( this ) );
425                 selects[ 1 ].on( 'choose', onUploadFormChange.bind( this ) );
426                 selects[ 2 ].on( 'choose', onUploadFormChange.bind( this ) );
428                 return this.uploadForm;
429         };
431         /**
432          * Test option 4, idea E from T121021. See T120867.
433          */
434         mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm4 = function () {
435                 var fieldset, $guide;
436                 this.renderUploadForm1();
437                 fieldset = this.uploadForm.getItems()[ 0 ];
439                 $guide = mw.template.get( 'mediawiki.ForeignStructuredUpload.BookletLayout', 'guide.html' ).render();
440                 $guide.find( '.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good span' )
441                         .msg( 'foreign-structured-upload-form-4-label-good' );
442                 $guide.find( '.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad span' )
443                         .msg( 'foreign-structured-upload-form-4-label-bad' );
445                 // Note the index, we insert after the SelectFileWidget field
446                 fieldset.addItems( [
447                         new OO.ui.FieldLayout( new OO.ui.Widget( {
448                                 $content: $guide
449                         } ), {
450                                 align: 'top'
451                         } )
452                 ], 1 );
454                 // Hook for custom styles
455                 fieldset.getItems()[ 2 ].$element.addClass( 'mw-foreignStructuredUpload-bookletLayout-guide-checkbox' );
457                 // Streamline: remove mention of local Special:Upload
458                 fieldset.getItems()[ 3 ].$element.find( 'p' ).last().remove();
460                 return this.uploadForm;
461         };
463         /**
464          * @inheritdoc
465          */
466         mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () {};
468         /**
469          * @inheritdoc
470          */
471         mw.ForeignStructuredUpload.BookletLayout.prototype.renderInfoForm = function () {
472                 var fieldset;
474                 this.filenameWidget = new OO.ui.TextInputWidget( {
475                         required: true,
476                         validate: /.+/
477                 } );
478                 this.descriptionWidget = new OO.ui.TextInputWidget( {
479                         required: true,
480                         validate: /\S+/,
481                         multiline: true,
482                         autosize: true
483                 } );
484                 this.categoriesWidget = new mw.widgets.CategorySelector( {
485                         // Can't be done here because we don't know the target wiki yet... done in #initialize.
486                         // api: new mw.ForeignApi( ... ),
487                         $overlay: this.$overlay
488                 } );
489                 this.dateWidget = new mw.widgets.DateInputWidget( {
490                         $overlay: this.$overlay,
491                         required: true,
492                         mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow
493                 } );
495                 fieldset = new OO.ui.FieldsetLayout( {
496                         label: mw.msg( 'upload-form-label-infoform-title' )
497                 } );
498                 fieldset.addItems( [
499                         new OO.ui.FieldLayout( this.filenameWidget, {
500                                 label: mw.msg( 'upload-form-label-infoform-name' ),
501                                 align: 'top',
502                                 help: mw.msg( 'upload-form-label-infoform-name-tooltip' )
503                         } ),
504                         new OO.ui.FieldLayout( this.descriptionWidget, {
505                                 label: mw.msg( 'upload-form-label-infoform-description' ),
506                                 align: 'top',
507                                 help: mw.msg( 'upload-form-label-infoform-description-tooltip' )
508                         } ),
509                         new OO.ui.FieldLayout( this.categoriesWidget, {
510                                 label: mw.msg( 'foreign-structured-upload-form-label-infoform-categories' ),
511                                 align: 'top'
512                         } ),
513                         new OO.ui.FieldLayout( this.dateWidget, {
514                                 label: mw.msg( 'foreign-structured-upload-form-label-infoform-date' ),
515                                 align: 'top'
516                         } )
517                 ] );
518                 this.infoForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
520                 // Validation
521                 this.filenameWidget.on( 'change', this.onInfoFormChange.bind( this ) );
522                 this.descriptionWidget.on( 'change', this.onInfoFormChange.bind( this ) );
523                 this.dateWidget.on( 'change', this.onInfoFormChange.bind( this ) );
525                 return this.infoForm;
526         };
528         /**
529          * @inheritdoc
530          */
531         mw.ForeignStructuredUpload.BookletLayout.prototype.onInfoFormChange = function () {
532                 var layout = this;
533                 $.when(
534                         this.filenameWidget.getValidity(),
535                         this.descriptionWidget.getValidity(),
536                         this.dateWidget.getValidity()
537                 ).done( function () {
538                         layout.emit( 'infoValid', true );
539                 } ).fail( function () {
540                         layout.emit( 'infoValid', false );
541                 } );
542         };
544         /* Getters */
546         /**
547          * @inheritdoc
548          */
549         mw.ForeignStructuredUpload.BookletLayout.prototype.getText = function () {
550                 var language = mw.config.get( 'wgContentLanguage' );
551                 this.upload.clearDescriptions();
552                 this.upload.addDescription( language, this.descriptionWidget.getValue() );
553                 this.upload.setDate( this.dateWidget.getValue() );
554                 this.upload.clearCategories();
555                 this.upload.addCategories( this.categoriesWidget.getItemsData() );
556                 return this.upload.getText();
557         };
559         /**
560          * Get original date from EXIF data
561          *
562          * @param {Object} file
563          * @return {jQuery.Promise} Promise resolved with the EXIF date
564          */
565         mw.ForeignStructuredUpload.BookletLayout.prototype.getDateFromExif = function ( file ) {
566                 var fileReader,
567                         deferred = $.Deferred();
569                 if ( file && file.type === 'image/jpeg' ) {
570                         fileReader = new FileReader();
571                         fileReader.onload = function () {
572                                 var fileStr, arr, i, metadata;
574                                 if ( typeof fileReader.result === 'string' ) {
575                                         fileStr = fileReader.result;
576                                 } else {
577                                         // Array buffer; convert to binary string for the library.
578                                         arr = new Uint8Array( fileReader.result );
579                                         fileStr = '';
580                                         for ( i = 0; i < arr.byteLength; i++ ) {
581                                                 fileStr += String.fromCharCode( arr[ i ] );
582                                         }
583                                 }
585                                 try {
586                                         metadata = mw.libs.jpegmeta( this.result, file.name );
587                                 } catch ( e ) {
588                                         metadata = null;
589                                 }
591                                 if ( metadata !== null && metadata.exif !== undefined && metadata.exif.DateTimeOriginal ) {
592                                         deferred.resolve( moment( metadata.exif.DateTimeOriginal, 'YYYY:MM:DD' ).format( 'YYYY-MM-DD' ) );
593                                 } else {
594                                         deferred.reject();
595                                 }
596                         };
598                         if ( 'readAsBinaryString' in fileReader ) {
599                                 fileReader.readAsBinaryString( file );
600                         } else if ( 'readAsArrayBuffer' in fileReader ) {
601                                 fileReader.readAsArrayBuffer( file );
602                         } else {
603                                 // We should never get here
604                                 deferred.reject();
605                                 throw new Error( 'Cannot read thumbnail as binary string or array buffer.' );
606                         }
607                 }
609                 return deferred.promise();
610         };
612         /**
613          * Get last modified date from file
614          *
615          * @param {Object} file
616          * @return {Object} Last modified date from file
617          */
618         mw.ForeignStructuredUpload.BookletLayout.prototype.getDateFromLastModified = function ( file ) {
619                 if ( file && file.lastModified ) {
620                         return moment( file.lastModified ).format( 'YYYY-MM-DD' );
621                 }
622         };
624         /* Setters */
626         /**
627          * @inheritdoc
628          */
629         mw.ForeignStructuredUpload.BookletLayout.prototype.clear = function () {
630                 mw.ForeignStructuredUpload.BookletLayout.parent.prototype.clear.call( this );
632                 if ( this.ownWorkCheckbox ) {
633                         this.ownWorkCheckbox.setSelected( false );
634                 }
635                 if ( this.licenseCheckboxes ) {
636                         this.licenseCheckboxes.forEach( function ( checkbox ) {
637                                 checkbox.setSelected( false );
638                         } );
639                 }
640                 if ( this.licenseSelectFields ) {
641                         this.licenseSelectFields.forEach( function ( field, i ) {
642                                 field.fieldWidget.selectItem( null );
643                                 if ( i !== 0 ) {
644                                         field.toggle( false );
645                                 }
646                         } );
647                 }
649                 this.categoriesWidget.setItemsFromData( [] );
650                 this.dateWidget.setValue( '' ).setValidityFlag( true );
651         };
653 }( jQuery, mediaWiki ) );