Another attempt to fix the http blog redirects.
[WebKit-https.git] / Websites / webkit.org / blog / wp-includes / js / plupload / wp-plupload.js
1 /* global pluploadL10n, plupload, _wpPluploadSettings */
2
3 window.wp = window.wp || {};
4
5 ( function( exports, $ ) {
6         var Uploader;
7
8         if ( typeof _wpPluploadSettings === 'undefined' ) {
9                 return;
10         }
11
12         /**
13          * A WordPress uploader.
14          *
15          * The Plupload library provides cross-browser uploader UI integration.
16          * This object bridges the Plupload API to integrate uploads into the
17          * WordPress back-end and the WordPress media experience.
18          *
19          * @param {object} options           The options passed to the new plupload instance.
20          * @param {object} options.container The id of uploader container.
21          * @param {object} options.browser   The id of button to trigger the file select.
22          * @param {object} options.dropzone  The id of file drop target.
23          * @param {object} options.plupload  An object of parameters to pass to the plupload instance.
24          * @param {object} options.params    An object of parameters to pass to $_POST when uploading the file.
25          *                                   Extends this.plupload.multipart_params under the hood.
26          */
27         Uploader = function( options ) {
28                 var self = this,
29                         isIE = navigator.userAgent.indexOf('Trident/') != -1 || navigator.userAgent.indexOf('MSIE ') != -1,
30                         elements = {
31                                 container: 'container',
32                                 browser:   'browse_button',
33                                 dropzone:  'drop_element'
34                         },
35                         key, error;
36
37                 this.supports = {
38                         upload: Uploader.browser.supported
39                 };
40
41                 this.supported = this.supports.upload;
42
43                 if ( ! this.supported ) {
44                         return;
45                 }
46
47                 // Arguments to send to pluplad.Uploader().
48                 // Use deep extend to ensure that multipart_params and other objects are cloned.
49                 this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults );
50                 this.container = document.body; // Set default container.
51
52                 // Extend the instance with options.
53                 //
54                 // Use deep extend to allow options.plupload to override individual
55                 // default plupload keys.
56                 $.extend( true, this, options );
57
58                 // Proxy all methods so this always refers to the current instance.
59                 for ( key in this ) {
60                         if ( $.isFunction( this[ key ] ) ) {
61                                 this[ key ] = $.proxy( this[ key ], this );
62                         }
63                 }
64
65                 // Ensure all elements are jQuery elements and have id attributes,
66                 // then set the proper plupload arguments to the ids.
67                 for ( key in elements ) {
68                         if ( ! this[ key ] ) {
69                                 continue;
70                         }
71
72                         this[ key ] = $( this[ key ] ).first();
73
74                         if ( ! this[ key ].length ) {
75                                 delete this[ key ];
76                                 continue;
77                         }
78
79                         if ( ! this[ key ].prop('id') ) {
80                                 this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ );
81                         }
82
83                         this.plupload[ elements[ key ] ] = this[ key ].prop('id');
84                 }
85
86                 // If the uploader has neither a browse button nor a dropzone, bail.
87                 if ( ! ( this.browser && this.browser.length ) && ! ( this.dropzone && this.dropzone.length ) ) {
88                         return;
89                 }
90
91                 // Make sure flash sends cookies (seems in IE it does without switching to urlstream mode)
92                 if ( ! isIE && 'flash' === plupload.predictRuntime( this.plupload ) &&
93                         ( ! this.plupload.required_features || ! this.plupload.required_features.hasOwnProperty( 'send_binary_string' ) ) ) {
94
95                         this.plupload.required_features = this.plupload.required_features || {};
96                         this.plupload.required_features.send_binary_string = true;
97                 }
98
99                 // Initialize the plupload instance.
100                 this.uploader = new plupload.Uploader( this.plupload );
101                 delete this.plupload;
102
103                 // Set default params and remove this.params alias.
104                 this.param( this.params || {} );
105                 delete this.params;
106
107                 /**
108                  * Custom error callback.
109                  *
110                  * Add a new error to the errors collection, so other modules can track
111                  * and display errors. @see wp.Uploader.errors.
112                  *
113                  * @param  {string}        message
114                  * @param  {object}        data
115                  * @param  {plupload.File} file     File that was uploaded.
116                  */
117                 error = function( message, data, file ) {
118                         if ( file.attachment ) {
119                                 file.attachment.destroy();
120                         }
121
122                         Uploader.errors.unshift({
123                                 message: message || pluploadL10n.default_error,
124                                 data:    data,
125                                 file:    file
126                         });
127
128                         self.error( message, data, file );
129                 };
130
131                 /**
132                  * After the Uploader has been initialized, initialize some behaviors for the dropzone.
133                  *
134                  * @param {plupload.Uploader} uploader Uploader instance.
135                  */
136                 this.uploader.bind( 'init', function( uploader ) {
137                         var timer, active, dragdrop,
138                                 dropzone = self.dropzone;
139
140                         dragdrop = self.supports.dragdrop = uploader.features.dragdrop && ! Uploader.browser.mobile;
141
142                         // Generate drag/drop helper classes.
143                         if ( ! dropzone ) {
144                                 return;
145                         }
146
147                         dropzone.toggleClass( 'supports-drag-drop', !! dragdrop );
148
149                         if ( ! dragdrop ) {
150                                 return dropzone.unbind('.wp-uploader');
151                         }
152
153                         // 'dragenter' doesn't fire correctly, simulate it with a limited 'dragover'.
154                         dropzone.bind( 'dragover.wp-uploader', function() {
155                                 if ( timer ) {
156                                         clearTimeout( timer );
157                                 }
158
159                                 if ( active ) {
160                                         return;
161                                 }
162
163                                 dropzone.trigger('dropzone:enter').addClass('drag-over');
164                                 active = true;
165                         });
166
167                         dropzone.bind('dragleave.wp-uploader, drop.wp-uploader', function() {
168                                 // Using an instant timer prevents the drag-over class from
169                                 // being quickly removed and re-added when elements inside the
170                                 // dropzone are repositioned.
171                                 //
172                                 // @see https://core.trac.wordpress.org/ticket/21705
173                                 timer = setTimeout( function() {
174                                         active = false;
175                                         dropzone.trigger('dropzone:leave').removeClass('drag-over');
176                                 }, 0 );
177                         });
178
179                         self.ready = true;
180                         $(self).trigger( 'uploader:ready' );
181                 });
182
183                 this.uploader.init();
184
185                 if ( this.browser ) {
186                         this.browser.on( 'mouseenter', this.refresh );
187                 } else {
188                         this.uploader.disableBrowse( true );
189                         // If HTML5 mode, hide the auto-created file container.
190                         $('#' + this.uploader.id + '_html5_container').hide();
191                 }
192
193                 /**
194                  * After files were filtered and added to the queue, create a model for each.
195                  *
196                  * @event FilesAdded
197                  * @param {plupload.Uploader} uploader Uploader instance.
198                  * @param {Array}             files    Array of file objects that were added to queue by the user.
199                  */
200                 this.uploader.bind( 'FilesAdded', function( up, files ) {
201                         _.each( files, function( file ) {
202                                 var attributes, image;
203
204                                 // Ignore failed uploads.
205                                 if ( plupload.FAILED === file.status ) {
206                                         return;
207                                 }
208
209                                 // Generate attributes for a new `Attachment` model.
210                                 attributes = _.extend({
211                                         file:      file,
212                                         uploading: true,
213                                         date:      new Date(),
214                                         filename:  file.name,
215                                         menuOrder: 0,
216                                         uploadedTo: wp.media.model.settings.post.id
217                                 }, _.pick( file, 'loaded', 'size', 'percent' ) );
218
219                                 // Handle early mime type scanning for images.
220                                 image = /(?:jpe?g|png|gif)$/i.exec( file.name );
221
222                                 // For images set the model's type and subtype attributes.
223                                 if ( image ) {
224                                         attributes.type = 'image';
225
226                                         // `jpeg`, `png` and `gif` are valid subtypes.
227                                         // `jpg` is not, so map it to `jpeg`.
228                                         attributes.subtype = ( 'jpg' === image[0] ) ? 'jpeg' : image[0];
229                                 }
230
231                                 // Create a model for the attachment, and add it to the Upload queue collection
232                                 // so listeners to the upload queue can track and display upload progress.
233                                 file.attachment = wp.media.model.Attachment.create( attributes );
234                                 Uploader.queue.add( file.attachment );
235
236                                 self.added( file.attachment );
237                         });
238
239                         up.refresh();
240                         up.start();
241                 });
242
243                 this.uploader.bind( 'UploadProgress', function( up, file ) {
244                         file.attachment.set( _.pick( file, 'loaded', 'percent' ) );
245                         self.progress( file.attachment );
246                 });
247
248                 /**
249                  * After a file is successfully uploaded, update its model.
250                  *
251                  * @param {plupload.Uploader} uploader Uploader instance.
252                  * @param {plupload.File}     file     File that was uploaded.
253                  * @param {Object}            response Object with response properties.
254                  * @return {mixed}
255                  */
256                 this.uploader.bind( 'FileUploaded', function( up, file, response ) {
257                         var complete;
258
259                         try {
260                                 response = JSON.parse( response.response );
261                         } catch ( e ) {
262                                 return error( pluploadL10n.default_error, e, file );
263                         }
264
265                         if ( ! _.isObject( response ) || _.isUndefined( response.success ) )
266                                 return error( pluploadL10n.default_error, null, file );
267                         else if ( ! response.success )
268                                 return error( response.data && response.data.message, response.data, file );
269
270                         _.each(['file','loaded','size','percent'], function( key ) {
271                                 file.attachment.unset( key );
272                         });
273
274                         file.attachment.set( _.extend( response.data, { uploading: false }) );
275                         wp.media.model.Attachment.get( response.data.id, file.attachment );
276
277                         complete = Uploader.queue.all( function( attachment ) {
278                                 return ! attachment.get('uploading');
279                         });
280
281                         if ( complete )
282                                 Uploader.queue.reset();
283
284                         self.success( file.attachment );
285                 });
286
287                 /**
288                  * When plupload surfaces an error, send it to the error handler.
289                  *
290                  * @param {plupload.Uploader} uploader Uploader instance.
291                  * @param {Object}            error    Contains code, message and sometimes file and other details.
292                  */
293                 this.uploader.bind( 'Error', function( up, pluploadError ) {
294                         var message = pluploadL10n.default_error,
295                                 key;
296
297                         // Check for plupload errors.
298                         for ( key in Uploader.errorMap ) {
299                                 if ( pluploadError.code === plupload[ key ] ) {
300                                         message = Uploader.errorMap[ key ];
301
302                                         if ( _.isFunction( message ) ) {
303                                                 message = message( pluploadError.file, pluploadError );
304                                         }
305
306                                         break;
307                                 }
308                         }
309
310                         error( message, pluploadError, pluploadError.file );
311                         up.refresh();
312                 });
313
314                 this.uploader.bind( 'PostInit', function() {
315                         self.init();
316                 });
317         };
318
319         // Adds the 'defaults' and 'browser' properties.
320         $.extend( Uploader, _wpPluploadSettings );
321
322         Uploader.uuid = 0;
323
324         // Map Plupload error codes to user friendly error messages.
325         Uploader.errorMap = {
326                 'FAILED':                 pluploadL10n.upload_failed,
327                 'FILE_EXTENSION_ERROR':   pluploadL10n.invalid_filetype,
328                 'IMAGE_FORMAT_ERROR':     pluploadL10n.not_an_image,
329                 'IMAGE_MEMORY_ERROR':     pluploadL10n.image_memory_exceeded,
330                 'IMAGE_DIMENSIONS_ERROR': pluploadL10n.image_dimensions_exceeded,
331                 'GENERIC_ERROR':          pluploadL10n.upload_failed,
332                 'IO_ERROR':               pluploadL10n.io_error,
333                 'HTTP_ERROR':             pluploadL10n.http_error,
334                 'SECURITY_ERROR':         pluploadL10n.security_error,
335
336                 'FILE_SIZE_ERROR': function( file ) {
337                         return pluploadL10n.file_exceeds_size_limit.replace('%s', file.name);
338                 }
339         };
340
341         $.extend( Uploader.prototype, {
342                 /**
343                  * Acts as a shortcut to extending the uploader's multipart_params object.
344                  *
345                  * param( key )
346                  *    Returns the value of the key.
347                  *
348                  * param( key, value )
349                  *    Sets the value of a key.
350                  *
351                  * param( map )
352                  *    Sets values for a map of data.
353                  */
354                 param: function( key, value ) {
355                         if ( arguments.length === 1 && typeof key === 'string' ) {
356                                 return this.uploader.settings.multipart_params[ key ];
357                         }
358
359                         if ( arguments.length > 1 ) {
360                                 this.uploader.settings.multipart_params[ key ] = value;
361                         } else {
362                                 $.extend( this.uploader.settings.multipart_params, key );
363                         }
364                 },
365
366                 /**
367                  * Make a few internal event callbacks available on the wp.Uploader object
368                  * to change the Uploader internals if absolutely necessary.
369                  */
370                 init:     function() {},
371                 error:    function() {},
372                 success:  function() {},
373                 added:    function() {},
374                 progress: function() {},
375                 complete: function() {},
376                 refresh:  function() {
377                         var node, attached, container, id;
378
379                         if ( this.browser ) {
380                                 node = this.browser[0];
381
382                                 // Check if the browser node is in the DOM.
383                                 while ( node ) {
384                                         if ( node === document.body ) {
385                                                 attached = true;
386                                                 break;
387                                         }
388                                         node = node.parentNode;
389                                 }
390
391                                 // If the browser node is not attached to the DOM, use a
392                                 // temporary container to house it, as the browser button
393                                 // shims require the button to exist in the DOM at all times.
394                                 if ( ! attached ) {
395                                         id = 'wp-uploader-browser-' + this.uploader.id;
396
397                                         container = $( '#' + id );
398                                         if ( ! container.length ) {
399                                                 container = $('<div class="wp-uploader-browser" />').css({
400                                                         position: 'fixed',
401                                                         top: '-1000px',
402                                                         left: '-1000px',
403                                                         height: 0,
404                                                         width: 0
405                                                 }).attr( 'id', 'wp-uploader-browser-' + this.uploader.id ).appendTo('body');
406                                         }
407
408                                         container.append( this.browser );
409                                 }
410                         }
411
412                         this.uploader.refresh();
413                 }
414         });
415
416         // Create a collection of attachments in the upload queue,
417         // so that other modules can track and display upload progress.
418         Uploader.queue = new wp.media.model.Attachments( [], { query: false });
419
420         // Create a collection to collect errors incurred while attempting upload.
421         Uploader.errors = new Backbone.Collection();
422
423         exports.Uploader = Uploader;
424 })( wp, jQuery );