PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / modules / videopress / js / videopress-plupload.js

videopress-plupload.js in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at modules/videopress/js/videopress-plupload.js

521 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global pluploadL10n, plupload, _wpPluploadSettings */
2
3 window.wp = window.wp || {};
4
5 ( function ( exports, $ ) {
6 var Uploader, vp;
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 =
30 navigator.userAgent.indexOf( 'Trident/' ) !== -1 ||
31 navigator.userAgent.indexOf( 'MSIE ' ) !== -1,
32 elements = {
33 container: 'container',
34 browser: 'browse_button',
35 dropzone: 'drop_element',
36 },
37 key,
38 error;
39
40 this.supports = {
41 upload: Uploader.browser.supported,
42 };
43
44 this.supported = this.supports.upload;
45
46 if ( ! this.supported ) {
47 return;
48 }
49
50 // Arguments to send to pluplad.Uploader().
51 // Use deep extend to ensure that multipart_params and other objects are cloned.
52 this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults );
53 this.container = document.body; // Set default container.
54
55 // Extend the instance with options.
56 //
57 // Use deep extend to allow options.plupload to override individual
58 // default plupload keys.
59 $.extend( true, this, options );
60
61 // Proxy all methods so this always refers to the current instance.
62 for ( key in this ) {
63 if ( $.isFunction( this[ key ] ) ) {
64 this[ key ] = $.proxy( this[ key ], this );
65 }
66 }
67
68 // Ensure all elements are jQuery elements and have id attributes,
69 // then set the proper plupload arguments to the ids.
70 for ( key in elements ) {
71 if ( ! this[ key ] ) {
72 continue;
73 }
74
75 this[ key ] = $( this[ key ] ).first();
76
77 if ( ! this[ key ].length ) {
78 delete this[ key ];
79 continue;
80 }
81
82 if ( ! this[ key ].prop( 'id' ) ) {
83 this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ );
84 }
85
86 this.plupload[ elements[ key ] ] = this[ key ].prop( 'id' );
87 }
88
89 // If the uploader has neither a browse button nor a dropzone, bail.
90 if (
91 ! ( this.browser && this.browser.length ) &&
92 ! ( this.dropzone && this.dropzone.length )
93 ) {
94 return;
95 }
96
97 // Make sure flash sends cookies (seems in IE it does without switching to urlstream mode)
98 if (
99 ! isIE &&
100 'flash' === plupload.predictRuntime( this.plupload ) &&
101 ( ! this.plupload.required_features ||
102 ! Object.hasOwn( this.plupload.required_features, 'send_binary_string' ) )
103 ) {
104 this.plupload.required_features = this.plupload.required_features || {};
105 this.plupload.required_features.send_binary_string = true;
106 }
107
108 // Initialize the plupload instance.
109 this.uploader = new plupload.Uploader( this.plupload );
110 delete this.plupload;
111
112 // Set default params and remove this.params alias.
113 this.param( this.params || {} );
114 delete this.params;
115
116 // Make sure that the VideoPress object is available
117 if ( typeof exports.VideoPress !== 'undefined' ) {
118 vp = exports.VideoPress;
119 } else {
120 window.console &&
121 window.console.error( 'The VideoPress object was not loaded. Errors may occur.' );
122 }
123
124 /**
125 * Custom error callback.
126 *
127 * Add a new error to the errors collection, so other modules can track
128 * and display errors. {@see wp.Uploader.errors}.
129 *
130 * @param {string} message
131 * @param {object} data
132 * @param {plupload.File} file File that was uploaded.
133 */
134 error = function ( message, data, file ) {
135 if ( file.attachment ) {
136 file.attachment.destroy();
137 }
138
139 Uploader.errors.unshift( {
140 message: message || pluploadL10n.default_error,
141 data: data,
142 file: file,
143 } );
144
145 self.error( message, data, file );
146 };
147
148 /**
149 * After the Uploader has been initialized, initialize some behaviors for the dropzone.
150 *
151 * @param {plupload.Uploader} uploader Uploader instance.
152 */
153 this.uploader.bind( 'init', function ( uploader ) {
154 var timer,
155 active,
156 dragdrop,
157 dropzone = self.dropzone;
158
159 dragdrop = self.supports.dragdrop = uploader.features.dragdrop && ! Uploader.browser.mobile;
160
161 // Generate drag/drop helper classes.
162 if ( ! dropzone ) {
163 return;
164 }
165
166 dropzone.toggleClass( 'supports-drag-drop', !! dragdrop );
167
168 if ( ! dragdrop ) {
169 return dropzone.unbind( '.wp-uploader' );
170 }
171
172 // 'dragenter' doesn't fire correctly, simulate it with a limited 'dragover'.
173 dropzone.bind( 'dragover.wp-uploader', function () {
174 if ( timer ) {
175 clearTimeout( timer );
176 }
177
178 if ( active ) {
179 return;
180 }
181
182 dropzone.trigger( 'dropzone:enter' ).addClass( 'drag-over' );
183 active = true;
184 } );
185
186 dropzone.bind( 'dragleave.wp-uploader, drop.wp-uploader', function () {
187 // Using an instant timer prevents the drag-over class from
188 // being quickly removed and re-added when elements inside the
189 // dropzone are repositioned.
190 //
191 // @see https://core.trac.wordpress.org/ticket/21705
192 timer = setTimeout( function () {
193 active = false;
194 dropzone.trigger( 'dropzone:leave' ).removeClass( 'drag-over' );
195 }, 0 );
196 } );
197
198 self.ready = true;
199 $( self ).trigger( 'uploader:ready' );
200 } );
201
202 this.uploader.bind( 'postinit', function ( up ) {
203 up.refresh();
204 self.init();
205 } );
206
207 this.uploader.init();
208
209 if ( this.browser ) {
210 this.browser.on( 'mouseenter', this.refresh );
211 } else {
212 this.uploader.disableBrowse( true );
213 // If HTML5 mode, hide the auto-created file container.
214 $( '#' + this.uploader.id + '_html5_container' ).hide();
215 }
216
217 /**
218 * After files were filtered and added to the queue, create a model for each.
219 *
220 * @event FilesAdded
221 * @param {plupload.Uploader} uploader Uploader instance.
222 * @param {Array} files Array of file objects that were added to queue by the user.
223 */
224 this.uploader.bind( 'FilesAdded', function ( up, files ) {
225 for ( const file of files ) {
226 var attributes, image;
227
228 // Ignore failed uploads.
229 if ( plupload.FAILED === file.status ) {
230 return;
231 }
232
233 // Generate attributes for a new `Attachment` model.
234 attributes = {
235 file: file,
236 uploading: true,
237 date: new Date(),
238 filename: file.name,
239 menuOrder: 0,
240 uploadedTo: wp.media.model.settings.post.id,
241 ...Object.fromEntries(
242 Object.entries( file ).filter(
243 ( [ k ] ) => k === 'loaded' || k === 'size' || k === 'percent'
244 )
245 ),
246 };
247
248 // Handle early mime type scanning for images.
249 image = /(?:jpe?g|png|gif|webp)$/i.exec( file.name );
250
251 // For images set the model's type and subtype attributes.
252 if ( image ) {
253 attributes.type = 'image';
254
255 // `jpeg`, `png` and `gif` are valid subtypes.
256 // `jpg` is not, so map it to `jpeg`.
257 attributes.subtype = 'jpg' === image[ 0 ] ? 'jpeg' : image[ 0 ];
258 }
259
260 // Create a model for the attachment, and add it to the Upload queue collection
261 // so listeners to the upload queue can track and display upload progress.
262 file.attachment = wp.media.model.Attachment.create( attributes );
263 Uploader.queue.add( file.attachment );
264
265 self.added( file.attachment );
266 }
267
268 up.refresh();
269 up.start();
270 } );
271
272 this.uploader.bind( 'UploadProgress', function ( up, file ) {
273 file.attachment.set(
274 Object.fromEntries(
275 Object.entries( file ).filter( ( [ k ] ) => k === 'loaded' || k === 'percent' )
276 )
277 );
278 self.progress( file.attachment );
279 } );
280
281 /**
282 * After a file is successfully uploaded, update its model.
283 *
284 * @param {plupload.Uploader} uploader Uploader instance.
285 * @param {plupload.File} file File that was uploaded.
286 * @param {Object} response Object with response properties.
287 * @return {mixed}
288 */
289 this.uploader.bind( 'FileUploaded', function ( up, file, response ) {
290 var complete;
291
292 try {
293 response = JSON.parse( response.response );
294 } catch ( e ) {
295 return error( pluploadL10n.default_error, e, file );
296 }
297
298 if ( typeof response.media !== 'undefined' ) {
299 response = vp.handleRestApiResponse( response, file );
300 } else {
301 response = vp.handleStandardResponse( response, file );
302 }
303
304 for ( const k of [ 'file', 'loaded', 'size', 'percent' ] ) {
305 file.attachment.unset( k );
306 }
307
308 file.attachment.set( { ...response.data, uploading: false } );
309 var att = wp.media.model.Attachment.get( response.data.id, file.attachment );
310
311 /* Once the new "empty" attachment is added to the collection above, check if it exists on the server, then set the new data.
312 * If it happens to not exist on the server yet (xmlrpc delayed or not working), then the empty media item will still be on the page,
313 * so no need to do any special error handling here on the sync.
314 *
315 * This is only necessary if `vp.handleRestApiResponse` was used above, as it is what returns the "empty" media item.
316 */
317 if ( typeof response.media !== 'undefined' ) {
318 att.sync( 'read' ).then( function ( data ) {
319 wp.media.model.Attachment.get( att.id ).set( data );
320 } );
321 }
322
323 complete = Uploader.queue.all( function ( attachment ) {
324 return ! attachment.get( 'uploading' );
325 } );
326
327 if ( complete ) {
328 vp && vp.resetToOriginalOptions( up );
329 Uploader.queue.reset();
330 }
331
332 self.success( file.attachment );
333 } );
334
335 /**
336 * When plupload surfaces an error, send it to the error handler.
337 *
338 * @param {plupload.Uploader} uploader Uploader instance.
339 * @param {Object} error Contains code, message and sometimes file and other details.
340 */
341 this.uploader.bind( 'Error', function ( up, pluploadError ) {
342 var message = pluploadL10n.default_error;
343
344 // Check for plupload errors.
345 for ( var k in Uploader.errorMap ) {
346 if ( pluploadError.code === plupload[ k ] ) {
347 message = Uploader.errorMap[ k ];
348
349 if ( typeof message === 'function' ) {
350 message = message( pluploadError.file, pluploadError );
351 }
352
353 break;
354 }
355 }
356
357 if ( 'response' in pluploadError ) {
358 try {
359 var pluploadResponseObject = JSON.parse( pluploadError.response );
360 if ( typeof pluploadResponseObject === 'object' ) {
361 if (
362 'errors' in pluploadResponseObject &&
363 typeof pluploadResponseObject.errors === 'object'
364 ) {
365 pluploadResponseObject = pluploadResponseObject.errors.shift();
366 }
367
368 if ( 'message' in pluploadResponseObject ) {
369 message = pluploadResponseObject.message;
370 }
371 }
372 } catch {
373 // Do nothing ...
374 }
375 }
376
377 error( message, pluploadError, pluploadError.file );
378 vp && vp.resetToOriginalOptions( up );
379 up.refresh();
380 } );
381
382 /**
383 * Add in a way for the uploader to reset itself when uploads are complete.
384 */
385 this.uploader.bind( 'UploadComplete', function ( up ) {
386 vp && vp.resetToOriginalOptions( up );
387 } );
388
389 /**
390 * Before we upload, check to see if this file is a videopress upload, if so, set new options and save the old ones.
391 */
392 this.uploader.bind( 'BeforeUpload', function ( up, file ) {
393 if ( typeof file.videopress !== 'undefined' ) {
394 vp.originalOptions.url = up.getOption( 'url' );
395 vp.originalOptions.multipart_params = up.getOption( 'multipart_params' );
396 vp.originalOptions.file_data_name = up.getOption( 'file_data_name' );
397
398 up.setOption( 'file_data_name', 'media[]' );
399 up.setOption( 'url', file.videopress.upload_action_url );
400 up.setOption( 'headers', {
401 Authorization:
402 'X_UPLOAD_TOKEN token="' +
403 file.videopress.upload_token +
404 '" blog_id="' +
405 file.videopress.upload_blog_id +
406 '"',
407 } );
408 }
409 } );
410 };
411
412 // Adds the 'defaults' and 'browser' properties.
413 $.extend( Uploader, _wpPluploadSettings );
414
415 Uploader.uuid = 0;
416
417 // Map Plupload error codes to user friendly error messages.
418 Uploader.errorMap = {
419 FAILED: pluploadL10n.upload_failed,
420 FILE_EXTENSION_ERROR: pluploadL10n.invalid_filetype,
421 IMAGE_FORMAT_ERROR: pluploadL10n.not_an_image,
422 IMAGE_MEMORY_ERROR: pluploadL10n.image_memory_exceeded,
423 IMAGE_DIMENSIONS_ERROR: pluploadL10n.image_dimensions_exceeded,
424 GENERIC_ERROR: pluploadL10n.upload_failed,
425 IO_ERROR: pluploadL10n.io_error,
426 HTTP_ERROR: pluploadL10n.http_error,
427 SECURITY_ERROR: pluploadL10n.security_error,
428
429 FILE_SIZE_ERROR: function ( file ) {
430 return pluploadL10n.file_exceeds_size_limit.replace( '%s', file.name );
431 },
432 };
433
434 $.extend( Uploader.prototype, {
435 /**
436 * Acts as a shortcut to extending the uploader's multipart_params object.
437 *
438 * param( key )
439 * Returns the value of the key.
440 *
441 * param( key, value )
442 * Sets the value of a key.
443 *
444 * param( map )
445 * Sets values for a map of data.
446 */
447 param: function ( key, value ) {
448 if ( arguments.length === 1 && typeof key === 'string' ) {
449 return this.uploader.settings.multipart_params[ key ];
450 }
451
452 if ( arguments.length > 1 ) {
453 this.uploader.settings.multipart_params[ key ] = value;
454 } else {
455 $.extend( this.uploader.settings.multipart_params, key );
456 }
457 },
458
459 /**
460 * Make a few internal event callbacks available on the wp.Uploader object
461 * to change the Uploader internals if absolutely necessary.
462 */
463 init: function () {},
464 error: function () {},
465 success: function () {},
466 added: function () {},
467 progress: function () {},
468 complete: function () {},
469 refresh: function () {
470 var node, attached, container, id;
471
472 if ( this.browser ) {
473 node = this.browser[ 0 ];
474
475 // Check if the browser node is in the DOM.
476 while ( node ) {
477 if ( node === document.body ) {
478 attached = true;
479 break;
480 }
481 node = node.parentNode;
482 }
483
484 // If the browser node is not attached to the DOM, use a
485 // temporary container to house it, as the browser button
486 // shims require the button to exist in the DOM at all times.
487 if ( ! attached ) {
488 id = 'wp-uploader-browser-' + this.uploader.id;
489
490 container = $( '#' + id );
491 if ( ! container.length ) {
492 container = $( '<div class="wp-uploader-browser" />' )
493 .css( {
494 position: 'fixed',
495 top: '-1000px',
496 left: '-1000px',
497 height: 0,
498 width: 0,
499 } )
500 .attr( 'id', 'wp-uploader-browser-' + this.uploader.id )
501 .appendTo( 'body' );
502 }
503
504 container.append( this.browser );
505 }
506 }
507
508 this.uploader.refresh();
509 },
510 } );
511
512 // Create a collection of attachments in the upload queue,
513 // so that other modules can track and display upload progress.
514 Uploader.queue = new wp.media.model.Attachments( [], { query: false } );
515
516 // Create a collection to collect errors incurred while attempting upload.
517 Uploader.errors = new Backbone.Collection();
518
519 exports.Uploader = Uploader;
520 } )( wp, jQuery );
521