wp/wp-includes/js/plupload/wp-plupload.js
changeset 5 5e2f62d02dcd
parent 0 d970ebf37754
child 7 cf61fcea0001
equal deleted inserted replaced
4:346c88efed21 5:5e2f62d02dcd
       
     1 /* global pluploadL10n, plupload, _wpPluploadSettings */
       
     2 
     1 window.wp = window.wp || {};
     3 window.wp = window.wp || {};
     2 
     4 
     3 (function( exports, $ ) {
     5 ( function( exports, $ ) {
     4 	var Uploader;
     6 	var Uploader;
     5 
     7 
     6 	if ( typeof _wpPluploadSettings === 'undefined' )
     8 	if ( typeof _wpPluploadSettings === 'undefined' ) {
     7 		return;
     9 		return;
     8 
    10 	}
     9 	/*
    11 
    10 	 * An object that helps create a WordPress uploader using plupload.
    12 	/**
       
    13 	 * A WordPress uploader.
    11 	 *
    14 	 *
    12 	 * @param options - object - The options passed to the new plupload instance.
    15 	 * The Plupload library provides cross-browser uploader UI integration.
    13 	 *    Accepts the following parameters:
    16 	 * This object bridges the Plupload API to integrate uploads into the
    14 	 *    - container - The id of uploader container.
    17 	 * WordPress back-end and the WordPress media experience.
    15 	 *    - browser   - The id of button to trigger the file select.
       
    16 	 *    - dropzone  - The id of file drop target.
       
    17 	 *    - plupload  - An object of parameters to pass to the plupload instance.
       
    18 	 *    - params    - An object of parameters to pass to $_POST when uploading the file.
       
    19 	 *                  Extends this.plupload.multipart_params under the hood.
       
    20 	 *
    18 	 *
    21 	 * @param attributes - object - Attributes and methods for this specific instance.
    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.
    22 	 */
    26 	 */
    23 	Uploader = function( options ) {
    27 	Uploader = function( options ) {
    24 		var self = this,
    28 		var self = this,
       
    29 			isIE = navigator.userAgent.indexOf('Trident/') != -1 || navigator.userAgent.indexOf('MSIE ') != -1,
    25 			elements = {
    30 			elements = {
    26 				container: 'container',
    31 				container: 'container',
    27 				browser:   'browse_button',
    32 				browser:   'browse_button',
    28 				dropzone:  'drop_element'
    33 				dropzone:  'drop_element'
    29 			},
    34 			},
    33 			upload: Uploader.browser.supported
    38 			upload: Uploader.browser.supported
    34 		};
    39 		};
    35 
    40 
    36 		this.supported = this.supports.upload;
    41 		this.supported = this.supports.upload;
    37 
    42 
    38 		if ( ! this.supported )
    43 		if ( ! this.supported ) {
    39 			return;
    44 			return;
    40 
    45 		}
       
    46 
       
    47 		// Arguments to send to pluplad.Uploader().
    41 		// Use deep extend to ensure that multipart_params and other objects are cloned.
    48 		// Use deep extend to ensure that multipart_params and other objects are cloned.
    42 		this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults );
    49 		this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults );
    43 		this.container = document.body; // Set default container.
    50 		this.container = document.body; // Set default container.
    44 
    51 
    45 		// Extend the instance with options
    52 		// Extend the instance with options.
    46 		//
    53 		//
    47 		// Use deep extend to allow options.plupload to override individual
    54 		// Use deep extend to allow options.plupload to override individual
    48 		// default plupload keys.
    55 		// default plupload keys.
    49 		$.extend( true, this, options );
    56 		$.extend( true, this, options );
    50 
    57 
    51 		// Proxy all methods so this always refers to the current instance.
    58 		// Proxy all methods so this always refers to the current instance.
    52 		for ( key in this ) {
    59 		for ( key in this ) {
    53 			if ( $.isFunction( this[ key ] ) )
    60 			if ( $.isFunction( this[ key ] ) ) {
    54 				this[ key ] = $.proxy( this[ key ], this );
    61 				this[ key ] = $.proxy( this[ key ], this );
    55 		}
    62 			}
    56 
    63 		}
    57 		// Ensure all elements are jQuery elements and have id attributes
    64 
    58 		// Then set the proper plupload arguments to the ids.
    65 		// Ensure all elements are jQuery elements and have id attributes,
       
    66 		// then set the proper plupload arguments to the ids.
    59 		for ( key in elements ) {
    67 		for ( key in elements ) {
    60 			if ( ! this[ key ] )
    68 			if ( ! this[ key ] ) {
    61 				continue;
    69 				continue;
       
    70 			}
    62 
    71 
    63 			this[ key ] = $( this[ key ] ).first();
    72 			this[ key ] = $( this[ key ] ).first();
    64 
    73 
    65 			if ( ! this[ key ].length ) {
    74 			if ( ! this[ key ].length ) {
    66 				delete this[ key ];
    75 				delete this[ key ];
    67 				continue;
    76 				continue;
    68 			}
    77 			}
    69 
    78 
    70 			if ( ! this[ key ].prop('id') )
    79 			if ( ! this[ key ].prop('id') ) {
    71 				this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ );
    80 				this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ );
       
    81 			}
       
    82 
    72 			this.plupload[ elements[ key ] ] = this[ key ].prop('id');
    83 			this.plupload[ elements[ key ] ] = this[ key ].prop('id');
    73 		}
    84 		}
    74 
    85 
    75 		// If the uploader has neither a browse button nor a dropzone, bail.
    86 		// If the uploader has neither a browse button nor a dropzone, bail.
    76 		if ( ! ( this.browser && this.browser.length ) && ! ( this.dropzone && this.dropzone.length ) )
    87 		if ( ! ( this.browser && this.browser.length ) && ! ( this.dropzone && this.dropzone.length ) ) {
    77 			return;
    88 			return;
    78 
    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.
    79 		this.uploader = new plupload.Uploader( this.plupload );
   100 		this.uploader = new plupload.Uploader( this.plupload );
    80 		delete this.plupload;
   101 		delete this.plupload;
    81 
   102 
    82 		// Set default params and remove this.params alias.
   103 		// Set default params and remove this.params alias.
    83 		this.param( this.params || {} );
   104 		this.param( this.params || {} );
    84 		delete this.params;
   105 		delete this.params;
    85 
   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 		 */
    86 		error = function( message, data, file ) {
   117 		error = function( message, data, file ) {
    87 			if ( file.attachment )
   118 			if ( file.attachment ) {
    88 				file.attachment.destroy();
   119 				file.attachment.destroy();
       
   120 			}
    89 
   121 
    90 			Uploader.errors.unshift({
   122 			Uploader.errors.unshift({
    91 				message: message || pluploadL10n.default_error,
   123 				message: message || pluploadL10n.default_error,
    92 				data:    data,
   124 				data:    data,
    93 				file:    file
   125 				file:    file
    94 			});
   126 			});
    95 
   127 
    96 			self.error( message, data, file );
   128 			self.error( message, data, file );
    97 		};
   129 		};
    98 
   130 
    99 		this.uploader.init();
   131 		/**
   100 
   132 		 * After the Uploader has been initialized, initialize some behaviors for the dropzone.
   101 		this.supports.dragdrop = this.uploader.features.dragdrop && ! Uploader.browser.mobile;
   133 		 *
   102 
   134 		 * @param {plupload.Uploader} uploader Uploader instance.
   103 		// Generate drag/drop helper classes.
   135 		 */
   104 		(function( dropzone, supported ) {
   136 		this.uploader.bind( 'init', function( uploader ) {
   105 			var timer, active;
   137 			var timer, active, dragdrop,
   106 
   138 				dropzone = self.dropzone;
   107 			if ( ! dropzone )
   139 
       
   140 			dragdrop = self.supports.dragdrop = uploader.features.dragdrop && ! Uploader.browser.mobile;
       
   141 
       
   142 			// Generate drag/drop helper classes.
       
   143 			if ( ! dropzone ) {
   108 				return;
   144 				return;
   109 
   145 			}
   110 			dropzone.toggleClass( 'supports-drag-drop', !! supported );
   146 
   111 
   147 			dropzone.toggleClass( 'supports-drag-drop', !! dragdrop );
   112 			if ( ! supported )
   148 
       
   149 			if ( ! dragdrop ) {
   113 				return dropzone.unbind('.wp-uploader');
   150 				return dropzone.unbind('.wp-uploader');
   114 
   151 			}
   115 			// 'dragenter' doesn't fire correctly,
   152 
   116 			// simulate it with a limited 'dragover'
   153 			// 'dragenter' doesn't fire correctly, simulate it with a limited 'dragover'.
   117 			dropzone.bind( 'dragover.wp-uploader', function(){
   154 			dropzone.bind( 'dragover.wp-uploader', function() {
   118 				if ( timer )
   155 				if ( timer ) {
   119 					clearTimeout( timer );
   156 					clearTimeout( timer );
   120 
   157 				}
   121 				if ( active )
   158 
       
   159 				if ( active ) {
   122 					return;
   160 					return;
       
   161 				}
   123 
   162 
   124 				dropzone.trigger('dropzone:enter').addClass('drag-over');
   163 				dropzone.trigger('dropzone:enter').addClass('drag-over');
   125 				active = true;
   164 				active = true;
   126 			});
   165 			});
   127 
   166 
   128 			dropzone.bind('dragleave.wp-uploader, drop.wp-uploader', function(){
   167 			dropzone.bind('dragleave.wp-uploader, drop.wp-uploader', function() {
   129 				// Using an instant timer prevents the drag-over class from
   168 				// Using an instant timer prevents the drag-over class from
   130 				// being quickly removed and re-added when elements inside the
   169 				// being quickly removed and re-added when elements inside the
   131 				// dropzone are repositioned.
   170 				// dropzone are repositioned.
   132 				//
   171 				//
   133 				// See http://core.trac.wordpress.org/ticket/21705
   172 				// @see https://core.trac.wordpress.org/ticket/21705
   134 				timer = setTimeout( function() {
   173 				timer = setTimeout( function() {
   135 					active = false;
   174 					active = false;
   136 					dropzone.trigger('dropzone:leave').removeClass('drag-over');
   175 					dropzone.trigger('dropzone:leave').removeClass('drag-over');
   137 				}, 0 );
   176 				}, 0 );
   138 			});
   177 			});
   139 		}( this.dropzone, this.supports.dragdrop ));
   178 
       
   179 			self.ready = true;
       
   180 			$(self).trigger( 'uploader:ready' );
       
   181 		});
       
   182 
       
   183 		this.uploader.init();
   140 
   184 
   141 		if ( this.browser ) {
   185 		if ( this.browser ) {
   142 			this.browser.on( 'mouseenter', this.refresh );
   186 			this.browser.on( 'mouseenter', this.refresh );
   143 		} else {
   187 		} else {
   144 			this.uploader.disableBrowse( true );
   188 			this.uploader.disableBrowse( true );
   145 			// If HTML5 mode, hide the auto-created file container.
   189 			// If HTML5 mode, hide the auto-created file container.
   146 			$('#' + this.uploader.id + '_html5_container').hide();
   190 			$('#' + this.uploader.id + '_html5_container').hide();
   147 		}
   191 		}
   148 
   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 		 */
   149 		this.uploader.bind( 'FilesAdded', function( up, files ) {
   200 		this.uploader.bind( 'FilesAdded', function( up, files ) {
   150 			_.each( files, function( file ) {
   201 			_.each( files, function( file ) {
   151 				var attributes, image;
   202 				var attributes, image;
   152 
   203 
   153 				// Ignore failed uploads.
   204 				// Ignore failed uploads.
   154 				if ( plupload.FAILED === file.status )
   205 				if ( plupload.FAILED === file.status ) {
   155 					return;
   206 					return;
       
   207 				}
   156 
   208 
   157 				// Generate attributes for a new `Attachment` model.
   209 				// Generate attributes for a new `Attachment` model.
   158 				attributes = _.extend({
   210 				attributes = _.extend({
   159 					file:      file,
   211 					file:      file,
   160 					uploading: true,
   212 					uploading: true,
   165 				}, _.pick( file, 'loaded', 'size', 'percent' ) );
   217 				}, _.pick( file, 'loaded', 'size', 'percent' ) );
   166 
   218 
   167 				// Handle early mime type scanning for images.
   219 				// Handle early mime type scanning for images.
   168 				image = /(?:jpe?g|png|gif)$/i.exec( file.name );
   220 				image = /(?:jpe?g|png|gif)$/i.exec( file.name );
   169 
   221 
   170 				// Did we find an image?
   222 				// For images set the model's type and subtype attributes.
   171 				if ( image ) {
   223 				if ( image ) {
   172 					attributes.type = 'image';
   224 					attributes.type = 'image';
   173 
   225 
   174 					// `jpeg`, `png` and `gif` are valid subtypes.
   226 					// `jpeg`, `png` and `gif` are valid subtypes.
   175 					// `jpg` is not, so map it to `jpeg`.
   227 					// `jpg` is not, so map it to `jpeg`.
   176 					attributes.subtype = ( 'jpg' === image[0] ) ? 'jpeg' : image[0];
   228 					attributes.subtype = ( 'jpg' === image[0] ) ? 'jpeg' : image[0];
   177 				}
   229 				}
   178 
   230 
   179 				// Create the `Attachment`.
   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.
   180 				file.attachment = wp.media.model.Attachment.create( attributes );
   233 				file.attachment = wp.media.model.Attachment.create( attributes );
   181 
       
   182 				Uploader.queue.add( file.attachment );
   234 				Uploader.queue.add( file.attachment );
   183 
   235 
   184 				self.added( file.attachment );
   236 				self.added( file.attachment );
   185 			});
   237 			});
   186 
   238 
   191 		this.uploader.bind( 'UploadProgress', function( up, file ) {
   243 		this.uploader.bind( 'UploadProgress', function( up, file ) {
   192 			file.attachment.set( _.pick( file, 'loaded', 'percent' ) );
   244 			file.attachment.set( _.pick( file, 'loaded', 'percent' ) );
   193 			self.progress( file.attachment );
   245 			self.progress( file.attachment );
   194 		});
   246 		});
   195 
   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 		 */
   196 		this.uploader.bind( 'FileUploaded', function( up, file, response ) {
   256 		this.uploader.bind( 'FileUploaded', function( up, file, response ) {
   197 			var complete;
   257 			var complete;
   198 
   258 
   199 			try {
   259 			try {
   200 				response = JSON.parse( response.response );
   260 				response = JSON.parse( response.response );
   222 				Uploader.queue.reset();
   282 				Uploader.queue.reset();
   223 
   283 
   224 			self.success( file.attachment );
   284 			self.success( file.attachment );
   225 		});
   285 		});
   226 
   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 		 */
   227 		this.uploader.bind( 'Error', function( up, pluploadError ) {
   293 		this.uploader.bind( 'Error', function( up, pluploadError ) {
   228 			var message = pluploadL10n.default_error,
   294 			var message = pluploadL10n.default_error,
   229 				key;
   295 				key;
   230 
   296 
   231 			// Check for plupload errors.
   297 			// Check for plupload errors.
   232 			for ( key in Uploader.errorMap ) {
   298 			for ( key in Uploader.errorMap ) {
   233 				if ( pluploadError.code === plupload[ key ] ) {
   299 				if ( pluploadError.code === plupload[ key ] ) {
   234 					message = Uploader.errorMap[ key ];
   300 					message = Uploader.errorMap[ key ];
   235 					if ( _.isFunction( message ) )
   301 
       
   302 					if ( _.isFunction( message ) ) {
   236 						message = message( pluploadError.file, pluploadError );
   303 						message = message( pluploadError.file, pluploadError );
       
   304 					}
       
   305 
   237 					break;
   306 					break;
   238 				}
   307 				}
   239 			}
   308 			}
   240 
   309 
   241 			error( message, pluploadError, pluploadError.file );
   310 			error( message, pluploadError, pluploadError.file );
   242 			up.refresh();
   311 			up.refresh();
   243 		});
   312 		});
   244 
   313 
   245 		this.init();
   314 		this.uploader.bind( 'PostInit', function() {
       
   315 			self.init();
       
   316 		});
   246 	};
   317 	};
   247 
   318 
   248 	// Adds the 'defaults' and 'browser' properties.
   319 	// Adds the 'defaults' and 'browser' properties.
   249 	$.extend( Uploader, _wpPluploadSettings );
   320 	$.extend( Uploader, _wpPluploadSettings );
   250 
   321 
   251 	Uploader.uuid = 0;
   322 	Uploader.uuid = 0;
   252 
   323 
       
   324 	// Map Plupload error codes to user friendly error messages.
   253 	Uploader.errorMap = {
   325 	Uploader.errorMap = {
   254 		'FAILED':                 pluploadL10n.upload_failed,
   326 		'FAILED':                 pluploadL10n.upload_failed,
   255 		'FILE_EXTENSION_ERROR':   pluploadL10n.invalid_filetype,
   327 		'FILE_EXTENSION_ERROR':   pluploadL10n.invalid_filetype,
   256 		'IMAGE_FORMAT_ERROR':     pluploadL10n.not_an_image,
   328 		'IMAGE_FORMAT_ERROR':     pluploadL10n.not_an_image,
   257 		'IMAGE_MEMORY_ERROR':     pluploadL10n.image_memory_exceeded,
   329 		'IMAGE_MEMORY_ERROR':     pluploadL10n.image_memory_exceeded,
   278 		 *
   350 		 *
   279 		 * param( map )
   351 		 * param( map )
   280 		 *    Sets values for a map of data.
   352 		 *    Sets values for a map of data.
   281 		 */
   353 		 */
   282 		param: function( key, value ) {
   354 		param: function( key, value ) {
   283 			if ( arguments.length === 1 && typeof key === 'string' )
   355 			if ( arguments.length === 1 && typeof key === 'string' ) {
   284 				return this.uploader.settings.multipart_params[ key ];
   356 				return this.uploader.settings.multipart_params[ key ];
       
   357 			}
   285 
   358 
   286 			if ( arguments.length > 1 ) {
   359 			if ( arguments.length > 1 ) {
   287 				this.uploader.settings.multipart_params[ key ] = value;
   360 				this.uploader.settings.multipart_params[ key ] = value;
   288 			} else {
   361 			} else {
   289 				$.extend( this.uploader.settings.multipart_params, key );
   362 				$.extend( this.uploader.settings.multipart_params, key );
   290 			}
   363 			}
   291 		},
   364 		},
   292 
   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 		 */
   293 		init:     function() {},
   370 		init:     function() {},
   294 		error:    function() {},
   371 		error:    function() {},
   295 		success:  function() {},
   372 		success:  function() {},
   296 		added:    function() {},
   373 		added:    function() {},
   297 		progress: function() {},
   374 		progress: function() {},
   334 
   411 
   335 			this.uploader.refresh();
   412 			this.uploader.refresh();
   336 		}
   413 		}
   337 	});
   414 	});
   338 
   415 
       
   416 	// Create a collection of attachments in the upload queue,
       
   417 	// so that other modules can track and display upload progress.
   339 	Uploader.queue = new wp.media.model.Attachments( [], { query: false });
   418 	Uploader.queue = new wp.media.model.Attachments( [], { query: false });
       
   419 
       
   420 	// Create a collection to collect errors incurred while attempting upload.
   340 	Uploader.errors = new Backbone.Collection();
   421 	Uploader.errors = new Backbone.Collection();
   341 
   422 
   342 	exports.Uploader = Uploader;
   423 	exports.Uploader = Uploader;
   343 })( wp, jQuery );
   424 })( wp, jQuery );