426 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			426 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /* global pluploadL10n, plupload, _wpPluploadSettings */ | ||
|  | 
 | ||
|  | window.wp = window.wp || {}; | ||
|  | 
 | ||
|  | ( function( exports, $ ) { | ||
|  | 	var Uploader; | ||
|  | 
 | ||
|  | 	if ( typeof _wpPluploadSettings === 'undefined' ) { | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * A WordPress uploader. | ||
|  | 	 * | ||
|  | 	 * The Plupload library provides cross-browser uploader UI integration. | ||
|  | 	 * This object bridges the Plupload API to integrate uploads into the | ||
|  | 	 * WordPress back end and the WordPress media experience. | ||
|  | 	 * | ||
|  | 	 * @param {object} options           The options passed to the new plupload instance. | ||
|  | 	 * @param {object} options.container The id of uploader container. | ||
|  | 	 * @param {object} options.browser   The id of button to trigger the file select. | ||
|  | 	 * @param {object} options.dropzone  The id of file drop target. | ||
|  | 	 * @param {object} options.plupload  An object of parameters to pass to the plupload instance. | ||
|  | 	 * @param {object} options.params    An object of parameters to pass to $_POST when uploading the file. | ||
|  | 	 *                                   Extends this.plupload.multipart_params under the hood. | ||
|  | 	 */ | ||
|  | 	Uploader = function( options ) { | ||
|  | 		var self = this, | ||
|  | 			isIE = navigator.userAgent.indexOf('Trident/') != -1 || navigator.userAgent.indexOf('MSIE ') != -1, | ||
|  | 			elements = { | ||
|  | 				container: 'container', | ||
|  | 				browser:   'browse_button', | ||
|  | 				dropzone:  'drop_element' | ||
|  | 			}, | ||
|  | 			key, error; | ||
|  | 
 | ||
|  | 		this.supports = { | ||
|  | 			upload: Uploader.browser.supported | ||
|  | 		}; | ||
|  | 
 | ||
|  | 		this.supported = this.supports.upload; | ||
|  | 
 | ||
|  | 		if ( ! this.supported ) { | ||
|  | 			return; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Arguments to send to pluplad.Uploader().
 | ||
|  | 		// Use deep extend to ensure that multipart_params and other objects are cloned.
 | ||
|  | 		this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults ); | ||
|  | 		this.container = document.body; // Set default container.
 | ||
|  | 
 | ||
|  | 		// Extend the instance with options.
 | ||
|  | 		//
 | ||
|  | 		// Use deep extend to allow options.plupload to override individual
 | ||
|  | 		// default plupload keys.
 | ||
|  | 		$.extend( true, this, options ); | ||
|  | 
 | ||
|  | 		// Proxy all methods so this always refers to the current instance.
 | ||
|  | 		for ( key in this ) { | ||
|  | 			if ( $.isFunction( this[ key ] ) ) { | ||
|  | 				this[ key ] = $.proxy( this[ key ], this ); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Ensure all elements are jQuery elements and have id attributes,
 | ||
|  | 		// then set the proper plupload arguments to the ids.
 | ||
|  | 		for ( key in elements ) { | ||
|  | 			if ( ! this[ key ] ) { | ||
|  | 				continue; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			this[ key ] = $( this[ key ] ).first(); | ||
|  | 
 | ||
|  | 			if ( ! this[ key ].length ) { | ||
|  | 				delete this[ key ]; | ||
|  | 				continue; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( ! this[ key ].prop('id') ) { | ||
|  | 				this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ ); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			this.plupload[ elements[ key ] ] = this[ key ].prop('id'); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// If the uploader has neither a browse button nor a dropzone, bail.
 | ||
|  | 		if ( ! ( this.browser && this.browser.length ) && ! ( this.dropzone && this.dropzone.length ) ) { | ||
|  | 			return; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Make sure flash sends cookies (seems in IE it does without switching to urlstream mode)
 | ||
|  | 		if ( ! isIE && 'flash' === plupload.predictRuntime( this.plupload ) && | ||
|  | 			( ! this.plupload.required_features || ! this.plupload.required_features.hasOwnProperty( 'send_binary_string' ) ) ) { | ||
|  | 
 | ||
|  | 			this.plupload.required_features = this.plupload.required_features || {}; | ||
|  | 			this.plupload.required_features.send_binary_string = true; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Initialize the plupload instance.
 | ||
|  | 		this.uploader = new plupload.Uploader( this.plupload ); | ||
|  | 		delete this.plupload; | ||
|  | 
 | ||
|  | 		// Set default params and remove this.params alias.
 | ||
|  | 		this.param( this.params || {} ); | ||
|  | 		delete this.params; | ||
|  | 
 | ||
|  | 		/** | ||
|  | 		 * Custom error callback. | ||
|  | 		 * | ||
|  | 		 * Add a new error to the errors collection, so other modules can track | ||
|  | 		 * and display errors. @see wp.Uploader.errors. | ||
|  | 		 * | ||
|  | 		 * @param  {string}        message | ||
|  | 		 * @param  {object}        data | ||
|  | 		 * @param  {plupload.File} file     File that was uploaded. | ||
|  | 		 */ | ||
|  | 		error = function( message, data, file ) { | ||
|  | 			if ( file.attachment ) { | ||
|  | 				file.attachment.destroy(); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			Uploader.errors.unshift({ | ||
|  | 				message: message || pluploadL10n.default_error, | ||
|  | 				data:    data, | ||
|  | 				file:    file | ||
|  | 			}); | ||
|  | 
 | ||
|  | 			self.error( message, data, file ); | ||
|  | 		}; | ||
|  | 
 | ||
|  | 		/** | ||
|  | 		 * After the Uploader has been initialized, initialize some behaviors for the dropzone. | ||
|  | 		 * | ||
|  | 		 * @param {plupload.Uploader} uploader Uploader instance. | ||
|  | 		 */ | ||
|  | 		this.uploader.bind( 'init', function( uploader ) { | ||
|  | 			var timer, active, dragdrop, | ||
|  | 				dropzone = self.dropzone; | ||
|  | 
 | ||
|  | 			dragdrop = self.supports.dragdrop = uploader.features.dragdrop && ! Uploader.browser.mobile; | ||
|  | 
 | ||
|  | 			// Generate drag/drop helper classes.
 | ||
|  | 			if ( ! dropzone ) { | ||
|  | 				return; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			dropzone.toggleClass( 'supports-drag-drop', !! dragdrop ); | ||
|  | 
 | ||
|  | 			if ( ! dragdrop ) { | ||
|  | 				return dropzone.unbind('.wp-uploader'); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// 'dragenter' doesn't fire correctly, simulate it with a limited 'dragover'.
 | ||
|  | 			dropzone.bind( 'dragover.wp-uploader', function() { | ||
|  | 				if ( timer ) { | ||
|  | 					clearTimeout( timer ); | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if ( active ) { | ||
|  | 					return; | ||
|  | 				} | ||
|  | 
 | ||
|  | 				dropzone.trigger('dropzone:enter').addClass('drag-over'); | ||
|  | 				active = true; | ||
|  | 			}); | ||
|  | 
 | ||
|  | 			dropzone.bind('dragleave.wp-uploader, drop.wp-uploader', function() { | ||
|  | 				// Using an instant timer prevents the drag-over class from
 | ||
|  | 				// being quickly removed and re-added when elements inside the
 | ||
|  | 				// dropzone are repositioned.
 | ||
|  | 				//
 | ||
|  | 				// @see https://core.trac.wordpress.org/ticket/21705
 | ||
|  | 				timer = setTimeout( function() { | ||
|  | 					active = false; | ||
|  | 					dropzone.trigger('dropzone:leave').removeClass('drag-over'); | ||
|  | 				}, 0 ); | ||
|  | 			}); | ||
|  | 
 | ||
|  | 			self.ready = true; | ||
|  | 			$(self).trigger( 'uploader:ready' ); | ||
|  | 		}); | ||
|  | 
 | ||
|  | 		this.uploader.bind( 'postinit', function( up ) { | ||
|  | 			up.refresh(); | ||
|  | 			self.init(); | ||
|  | 		}); | ||
|  | 
 | ||
|  | 		this.uploader.init(); | ||
|  | 
 | ||
|  | 		if ( this.browser ) { | ||
|  | 			this.browser.on( 'mouseenter', this.refresh ); | ||
|  | 		} else { | ||
|  | 			this.uploader.disableBrowse( true ); | ||
|  | 			// If HTML5 mode, hide the auto-created file container.
 | ||
|  | 			$('#' + this.uploader.id + '_html5_container').hide(); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		/** | ||
|  | 		 * After files were filtered and added to the queue, create a model for each. | ||
|  | 		 * | ||
|  | 		 * @param {plupload.Uploader} uploader Uploader instance. | ||
|  | 		 * @param {Array}             files    Array of file objects that were added to queue by the user. | ||
|  | 		 */ | ||
|  | 		this.uploader.bind( 'FilesAdded', function( up, files ) { | ||
|  | 			_.each( files, function( file ) { | ||
|  | 				var attributes, image; | ||
|  | 
 | ||
|  | 				// Ignore failed uploads.
 | ||
|  | 				if ( plupload.FAILED === file.status ) { | ||
|  | 					return; | ||
|  | 				} | ||
|  | 
 | ||
|  | 				// Generate attributes for a new `Attachment` model.
 | ||
|  | 				attributes = _.extend({ | ||
|  | 					file:      file, | ||
|  | 					uploading: true, | ||
|  | 					date:      new Date(), | ||
|  | 					filename:  file.name, | ||
|  | 					menuOrder: 0, | ||
|  | 					uploadedTo: wp.media.model.settings.post.id | ||
|  | 				}, _.pick( file, 'loaded', 'size', 'percent' ) ); | ||
|  | 
 | ||
|  | 				// Handle early mime type scanning for images.
 | ||
|  | 				image = /(?:jpe?g|png|gif)$/i.exec( file.name ); | ||
|  | 
 | ||
|  | 				// For images set the model's type and subtype attributes.
 | ||
|  | 				if ( image ) { | ||
|  | 					attributes.type = 'image'; | ||
|  | 
 | ||
|  | 					// `jpeg`, `png` and `gif` are valid subtypes.
 | ||
|  | 					// `jpg` is not, so map it to `jpeg`.
 | ||
|  | 					attributes.subtype = ( 'jpg' === image[0] ) ? 'jpeg' : image[0]; | ||
|  | 				} | ||
|  | 
 | ||
|  | 				// Create a model for the attachment, and add it to the Upload queue collection
 | ||
|  | 				// so listeners to the upload queue can track and display upload progress.
 | ||
|  | 				file.attachment = wp.media.model.Attachment.create( attributes ); | ||
|  | 				Uploader.queue.add( file.attachment ); | ||
|  | 
 | ||
|  | 				self.added( file.attachment ); | ||
|  | 			}); | ||
|  | 
 | ||
|  | 			up.refresh(); | ||
|  | 			up.start(); | ||
|  | 		}); | ||
|  | 
 | ||
|  | 		this.uploader.bind( 'UploadProgress', function( up, file ) { | ||
|  | 			file.attachment.set( _.pick( file, 'loaded', 'percent' ) ); | ||
|  | 			self.progress( file.attachment ); | ||
|  | 		}); | ||
|  | 
 | ||
|  | 		/** | ||
|  | 		 * After a file is successfully uploaded, update its model. | ||
|  | 		 * | ||
|  | 		 * @param {plupload.Uploader} uploader Uploader instance. | ||
|  | 		 * @param {plupload.File}     file     File that was uploaded. | ||
|  | 		 * @param {Object}            response Object with response properties. | ||
|  | 		 * @return {mixed} | ||
|  | 		 */ | ||
|  | 		this.uploader.bind( 'FileUploaded', function( up, file, response ) { | ||
|  | 			var complete; | ||
|  | 
 | ||
|  | 			try { | ||
|  | 				response = JSON.parse( response.response ); | ||
|  | 			} catch ( e ) { | ||
|  | 				return error( pluploadL10n.default_error, e, file ); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( ! _.isObject( response ) || _.isUndefined( response.success ) ) | ||
|  | 				return error( pluploadL10n.default_error, null, file ); | ||
|  | 			else if ( ! response.success ) | ||
|  | 				return error( response.data && response.data.message, response.data, file ); | ||
|  | 
 | ||
|  | 			_.each(['file','loaded','size','percent'], function( key ) { | ||
|  | 				file.attachment.unset( key ); | ||
|  | 			}); | ||
|  | 
 | ||
|  | 			file.attachment.set( _.extend( response.data, { uploading: false }) ); | ||
|  | 			wp.media.model.Attachment.get( response.data.id, file.attachment ); | ||
|  | 
 | ||
|  | 			complete = Uploader.queue.all( function( attachment ) { | ||
|  | 				return ! attachment.get('uploading'); | ||
|  | 			}); | ||
|  | 
 | ||
|  | 			if ( complete ) | ||
|  | 				Uploader.queue.reset(); | ||
|  | 
 | ||
|  | 			self.success( file.attachment ); | ||
|  | 		}); | ||
|  | 
 | ||
|  | 		/** | ||
|  | 		 * When plupload surfaces an error, send it to the error handler. | ||
|  | 		 * | ||
|  | 		 * @param {plupload.Uploader} uploader Uploader instance. | ||
|  | 		 * @param {Object}            error    Contains code, message and sometimes file and other details. | ||
|  | 		 */ | ||
|  | 		this.uploader.bind( 'Error', function( up, pluploadError ) { | ||
|  | 			var message = pluploadL10n.default_error, | ||
|  | 				key; | ||
|  | 
 | ||
|  | 			// Check for plupload errors.
 | ||
|  | 			for ( key in Uploader.errorMap ) { | ||
|  | 				if ( pluploadError.code === plupload[ key ] ) { | ||
|  | 					message = Uploader.errorMap[ key ]; | ||
|  | 
 | ||
|  | 					if ( _.isFunction( message ) ) { | ||
|  | 						message = message( pluploadError.file, pluploadError ); | ||
|  | 					} | ||
|  | 
 | ||
|  | 					break; | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			error( message, pluploadError, pluploadError.file ); | ||
|  | 			up.refresh(); | ||
|  | 		}); | ||
|  | 
 | ||
|  | 	}; | ||
|  | 
 | ||
|  | 	// Adds the 'defaults' and 'browser' properties.
 | ||
|  | 	$.extend( Uploader, _wpPluploadSettings ); | ||
|  | 
 | ||
|  | 	Uploader.uuid = 0; | ||
|  | 
 | ||
|  | 	// Map Plupload error codes to user friendly error messages.
 | ||
|  | 	Uploader.errorMap = { | ||
|  | 		'FAILED':                 pluploadL10n.upload_failed, | ||
|  | 		'FILE_EXTENSION_ERROR':   pluploadL10n.invalid_filetype, | ||
|  | 		'IMAGE_FORMAT_ERROR':     pluploadL10n.not_an_image, | ||
|  | 		'IMAGE_MEMORY_ERROR':     pluploadL10n.image_memory_exceeded, | ||
|  | 		'IMAGE_DIMENSIONS_ERROR': pluploadL10n.image_dimensions_exceeded, | ||
|  | 		'GENERIC_ERROR':          pluploadL10n.upload_failed, | ||
|  | 		'IO_ERROR':               pluploadL10n.io_error, | ||
|  | 		'HTTP_ERROR':             pluploadL10n.http_error, | ||
|  | 		'SECURITY_ERROR':         pluploadL10n.security_error, | ||
|  | 
 | ||
|  | 		'FILE_SIZE_ERROR': function( file ) { | ||
|  | 			return pluploadL10n.file_exceeds_size_limit.replace('%s', file.name); | ||
|  | 		} | ||
|  | 	}; | ||
|  | 
 | ||
|  | 	$.extend( Uploader.prototype, /** @lends wp.Uploader.prototype */{ | ||
|  | 		/** | ||
|  | 		 * Acts as a shortcut to extending the uploader's multipart_params object. | ||
|  | 		 * | ||
|  | 		 * param( key ) | ||
|  | 		 *    Returns the value of the key. | ||
|  | 		 * | ||
|  | 		 * param( key, value ) | ||
|  | 		 *    Sets the value of a key. | ||
|  | 		 * | ||
|  | 		 * param( map ) | ||
|  | 		 *    Sets values for a map of data. | ||
|  | 		 */ | ||
|  | 		param: function( key, value ) { | ||
|  | 			if ( arguments.length === 1 && typeof key === 'string' ) { | ||
|  | 				return this.uploader.settings.multipart_params[ key ]; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ( arguments.length > 1 ) { | ||
|  | 				this.uploader.settings.multipart_params[ key ] = value; | ||
|  | 			} else { | ||
|  | 				$.extend( this.uploader.settings.multipart_params, key ); | ||
|  | 			} | ||
|  | 		}, | ||
|  | 
 | ||
|  | 		/** | ||
|  | 		 * Make a few internal event callbacks available on the wp.Uploader object | ||
|  | 		 * to change the Uploader internals if absolutely necessary. | ||
|  | 		 */ | ||
|  | 		init:     function() {}, | ||
|  | 		error:    function() {}, | ||
|  | 		success:  function() {}, | ||
|  | 		added:    function() {}, | ||
|  | 		progress: function() {}, | ||
|  | 		complete: function() {}, | ||
|  | 		refresh:  function() { | ||
|  | 			var node, attached, container, id; | ||
|  | 
 | ||
|  | 			if ( this.browser ) { | ||
|  | 				node = this.browser[0]; | ||
|  | 
 | ||
|  | 				// Check if the browser node is in the DOM.
 | ||
|  | 				while ( node ) { | ||
|  | 					if ( node === document.body ) { | ||
|  | 						attached = true; | ||
|  | 						break; | ||
|  | 					} | ||
|  | 					node = node.parentNode; | ||
|  | 				} | ||
|  | 
 | ||
|  | 				// If the browser node is not attached to the DOM, use a
 | ||
|  | 				// temporary container to house it, as the browser button
 | ||
|  | 				// shims require the button to exist in the DOM at all times.
 | ||
|  | 				if ( ! attached ) { | ||
|  | 					id = 'wp-uploader-browser-' + this.uploader.id; | ||
|  | 
 | ||
|  | 					container = $( '#' + id ); | ||
|  | 					if ( ! container.length ) { | ||
|  | 						container = $('<div class="wp-uploader-browser" />').css({ | ||
|  | 							position: 'fixed', | ||
|  | 							top: '-1000px', | ||
|  | 							left: '-1000px', | ||
|  | 							height: 0, | ||
|  | 							width: 0 | ||
|  | 						}).attr( 'id', 'wp-uploader-browser-' + this.uploader.id ).appendTo('body'); | ||
|  | 					} | ||
|  | 
 | ||
|  | 					container.append( this.browser ); | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			this.uploader.refresh(); | ||
|  | 		} | ||
|  | 	}); | ||
|  | 
 | ||
|  | 	// Create a collection of attachments in the upload queue,
 | ||
|  | 	// so that other modules can track and display upload progress.
 | ||
|  | 	Uploader.queue = new wp.media.model.Attachments( [], { query: false }); | ||
|  | 
 | ||
|  | 	// Create a collection to collect errors incurred while attempting upload.
 | ||
|  | 	Uploader.errors = new Backbone.Collection(); | ||
|  | 
 | ||
|  | 	exports.Uploader = Uploader; | ||
|  | })( wp, jQuery ); |