wp/wp-includes/js/media-models.js
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
   114  *
   114  *
   115  * @alias wp.media
   115  * @alias wp.media
   116  * @memberOf wp
   116  * @memberOf wp
   117  * @namespace
   117  * @namespace
   118  *
   118  *
   119  * @param  {object} attributes The properties passed to the main media controller.
   119  * @param {Object} attributes The properties passed to the main media controller.
   120  * @return {wp.media.view.MediaFrame} A media workflow.
   120  * @return {wp.media.view.MediaFrame} A media workflow.
   121  */
   121  */
   122 media = wp.media = function( attributes ) {
   122 media = wp.media = function( attributes ) {
   123 	var MediaFrame = media.view.MediaFrame,
   123 	var MediaFrame = media.view.MediaFrame,
   124 		frame;
   124 		frame;
   183 /**
   183 /**
   184  * A basic equality comparator for Backbone models.
   184  * A basic equality comparator for Backbone models.
   185  *
   185  *
   186  * Used to order models within a collection - @see wp.media.model.Attachments.comparator().
   186  * Used to order models within a collection - @see wp.media.model.Attachments.comparator().
   187  *
   187  *
   188  * @param  {mixed}  a  The primary parameter to compare.
   188  * @param {mixed}  a  The primary parameter to compare.
   189  * @param  {mixed}  b  The primary parameter to compare.
   189  * @param {mixed}  b  The primary parameter to compare.
   190  * @param  {string} ac The fallback parameter to compare, a's cid.
   190  * @param {string} ac The fallback parameter to compare, a's cid.
   191  * @param  {string} bc The fallback parameter to compare, b's cid.
   191  * @param {string} bc The fallback parameter to compare, b's cid.
   192  * @return {number}    -1: a should come before b.
   192  * @return {number} -1: a should come before b.
   193  *                      0: a and b are of the same rank.
   193  *                   0: a and b are of the same rank.
   194  *                      1: b should come before a.
   194  *                   1: b should come before a.
   195  */
   195  */
   196 media.compare = function( a, b, ac, bc ) {
   196 media.compare = function( a, b, ac, bc ) {
   197 	if ( _.isEqual( a, b ) ) {
   197 	if ( _.isEqual( a, b ) ) {
   198 		return ac === bc ? 0 : (ac > bc ? -1 : 1);
   198 		return ac === bc ? 0 : (ac > bc ? -1 : 1);
   199 	} else {
   199 	} else {
   235 
   235 
   236 	/**
   236 	/**
   237 	 * Scales a set of dimensions to fit within bounding dimensions.
   237 	 * Scales a set of dimensions to fit within bounding dimensions.
   238 	 *
   238 	 *
   239 	 * @param {Object} dimensions
   239 	 * @param {Object} dimensions
   240 	 * @returns {Object}
   240 	 * @return {Object}
   241 	 */
   241 	 */
   242 	fit: function( dimensions ) {
   242 	fit: function( dimensions ) {
   243 		var width     = dimensions.width,
   243 		var width     = dimensions.width,
   244 			height    = dimensions.height,
   244 			height    = dimensions.height,
   245 			maxWidth  = dimensions.maxWidth,
   245 			maxWidth  = dimensions.maxWidth,
   246 			maxHeight = dimensions.maxHeight,
   246 			maxHeight = dimensions.maxHeight,
   247 			constraint;
   247 			constraint;
   248 
   248 
   249 		// Compare ratios between the two values to determine which
   249 		/*
   250 		// max to constrain by. If a max value doesn't exist, then the
   250 		 * Compare ratios between the two values to determine
   251 		// opposite side is the constraint.
   251 		 * which max to constrain by. If a max value doesn't exist,
       
   252 		 * then the opposite side is the constraint.
       
   253 		 */
   252 		if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) {
   254 		if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) {
   253 			constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height';
   255 			constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height';
   254 		} else if ( _.isUndefined( maxHeight ) ) {
   256 		} else if ( _.isUndefined( maxHeight ) ) {
   255 			constraint = 'width';
   257 			constraint = 'width';
   256 		} else if (  _.isUndefined( maxWidth ) && height > maxHeight ) {
   258 		} else if (  _.isUndefined( maxWidth ) && height > maxHeight ) {
   278 	},
   280 	},
   279 	/**
   281 	/**
   280 	 * Truncates a string by injecting an ellipsis into the middle.
   282 	 * Truncates a string by injecting an ellipsis into the middle.
   281 	 * Useful for filenames.
   283 	 * Useful for filenames.
   282 	 *
   284 	 *
   283 	 * @param {String} string
   285 	 * @param {string} string
   284 	 * @param {Number} [length=30]
   286 	 * @param {number} [length=30]
   285 	 * @param {String} [replacement=…]
   287 	 * @param {string} [replacement=…]
   286 	 * @returns {String} The string, unless length is greater than string.length.
   288 	 * @return {string} The string, unless length is greater than string.length.
   287 	 */
   289 	 */
   288 	truncate: function( string, length, replacement ) {
   290 	truncate: function( string, length, replacement ) {
   289 		length = length || 30;
   291 		length = length || 30;
   290 		replacement = replacement || '…';
   292 		replacement = replacement || '…';
   291 
   293 
   304  */
   306  */
   305 /**
   307 /**
   306  * wp.media.attachment
   308  * wp.media.attachment
   307  *
   309  *
   308  * @static
   310  * @static
   309  * @param {String} id A string used to identify a model.
   311  * @param {string} id A string used to identify a model.
   310  * @returns {wp.media.model.Attachment}
   312  * @return {wp.media.model.Attachment}
   311  */
   313  */
   312 media.attachment = function( id ) {
   314 media.attachment = function( id ) {
   313 	return Attachment.get( id );
   315 	return Attachment.get( id );
   314 };
   316 };
   315 
   317 
   324 /**
   326 /**
   325  * wp.media.query
   327  * wp.media.query
   326  *
   328  *
   327  * Shorthand for creating a new Attachments Query.
   329  * Shorthand for creating a new Attachments Query.
   328  *
   330  *
   329  * @param {object} [props]
   331  * @param {Object} [props]
   330  * @returns {wp.media.model.Attachments}
   332  * @return {wp.media.model.Attachments}
   331  */
   333  */
   332 media.query = function( props ) {
   334 media.query = function( props ) {
   333 	return new Attachments( null, {
   335 	return new Attachments( null, {
   334 		props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
   336 		props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
   335 	});
   337 	});
   336 };
   338 };
   337 
   339 
   338 // Clean up. Prevents mobile browsers caching
   340 // Clean up. Prevents mobile browsers caching.
   339 $(window).on('unload', function(){
   341 $(window).on('unload', function(){
   340 	window.wp = null;
   342 	window.wp = null;
   341 });
   343 });
   342 
   344 
   343 
   345 
   364 	 *
   366 	 *
   365 	 * @param {string} method
   367 	 * @param {string} method
   366 	 * @param {wp.media.model.Attachment} model
   368 	 * @param {wp.media.model.Attachment} model
   367 	 * @param {Object} [options={}]
   369 	 * @param {Object} [options={}]
   368 	 *
   370 	 *
   369 	 * @returns {Promise}
   371 	 * @return {Promise}
   370 	 */
   372 	 */
   371 	sync: function( method, model, options ) {
   373 	sync: function( method, model, options ) {
   372 		// If the attachment does not yet have an `id`, return an instantly
   374 		// If the attachment does not yet have an `id`, return an instantly
   373 		// rejected promise. Otherwise, all of our requests will fail.
   375 		// rejected promise. Otherwise, all of our requests will fail.
   374 		if ( _.isUndefined( this.id ) ) {
   376 		if ( _.isUndefined( this.id ) ) {
   385 			});
   387 			});
   386 			return wp.media.ajax( options );
   388 			return wp.media.ajax( options );
   387 
   389 
   388 		// Overload the `update` request so properties can be saved.
   390 		// Overload the `update` request so properties can be saved.
   389 		} else if ( 'update' === method ) {
   391 		} else if ( 'update' === method ) {
   390 			// If we do not have the necessary nonce, fail immeditately.
   392 			// If we do not have the necessary nonce, fail immediately.
   391 			if ( ! this.get('nonces') || ! this.get('nonces').update ) {
   393 			if ( ! this.get('nonces') || ! this.get('nonces').update ) {
   392 				return $.Deferred().rejectWith( this ).promise();
   394 				return $.Deferred().rejectWith( this ).promise();
   393 			}
   395 			}
   394 
   396 
   395 			options = options || {};
   397 			options = options || {};
   446 	},
   448 	},
   447 	/**
   449 	/**
   448 	 * Convert date strings into Date objects.
   450 	 * Convert date strings into Date objects.
   449 	 *
   451 	 *
   450 	 * @param {Object} resp The raw response object, typically returned by fetch()
   452 	 * @param {Object} resp The raw response object, typically returned by fetch()
   451 	 * @returns {Object} The modified response object, which is the attributes hash
   453 	 * @return {Object} The modified response object, which is the attributes hash
   452 	 *    to be set on the model.
   454 	 *                  to be set on the model.
   453 	 */
   455 	 */
   454 	parse: function( resp ) {
   456 	parse: function( resp ) {
   455 		if ( ! resp ) {
   457 		if ( ! resp ) {
   456 			return resp;
   458 			return resp;
   457 		}
   459 		}
   464 	 * @param {Object} data The properties to be saved.
   466 	 * @param {Object} data The properties to be saved.
   465 	 * @param {Object} options Sync options. e.g. patch, wait, success, error.
   467 	 * @param {Object} options Sync options. e.g. patch, wait, success, error.
   466 	 *
   468 	 *
   467 	 * @this Backbone.Model
   469 	 * @this Backbone.Model
   468 	 *
   470 	 *
   469 	 * @returns {Promise}
   471 	 * @return {Promise}
   470 	 */
   472 	 */
   471 	saveCompat: function( data, options ) {
   473 	saveCompat: function( data, options ) {
   472 		var model = this;
   474 		var model = this;
   473 
   475 
   474 		// If we do not have the necessary nonce, fail immeditately.
   476 		// If we do not have the necessary nonce, fail immediately.
   475 		if ( ! this.get('nonces') || ! this.get('nonces').update ) {
   477 		if ( ! this.get('nonces') || ! this.get('nonces').update ) {
   476 			return $.Deferred().rejectWith( this ).promise();
   478 			return $.Deferred().rejectWith( this ).promise();
   477 		}
   479 		}
   478 
   480 
   479 		return wp.media.post( 'save-attachment-compat', _.defaults({
   481 		return wp.media.post( 'save-attachment-compat', _.defaults({
   489 	 * Create a new model on the static 'all' attachments collection and return it.
   491 	 * Create a new model on the static 'all' attachments collection and return it.
   490 	 *
   492 	 *
   491 	 * @static
   493 	 * @static
   492 	 *
   494 	 *
   493 	 * @param {Object} attrs
   495 	 * @param {Object} attrs
   494 	 * @returns {wp.media.model.Attachment}
   496 	 * @return {wp.media.model.Attachment}
   495 	 */
   497 	 */
   496 	create: function( attrs ) {
   498 	create: function( attrs ) {
   497 		var Attachments = wp.media.model.Attachments;
   499 		var Attachments = wp.media.model.Attachments;
   498 		return Attachments.all.push( attrs );
   500 		return Attachments.all.push( attrs );
   499 	},
   501 	},
   504 	 * it returns the specified attachment.
   506 	 * it returns the specified attachment.
   505 	 *
   507 	 *
   506 	 * @static
   508 	 * @static
   507 	 * @param {string} id A string used to identify a model.
   509 	 * @param {string} id A string used to identify a model.
   508 	 * @param {Backbone.Model|undefined} attachment
   510 	 * @param {Backbone.Model|undefined} attachment
   509 	 * @returns {wp.media.model.Attachment}
   511 	 * @return {wp.media.model.Attachment}
   510 	 */
   512 	 */
   511 	get: _.memoize( function( id, attachment ) {
   513 	get: _.memoize( function( id, attachment ) {
   512 		var Attachments = wp.media.model.Attachments;
   514 		var Attachments = wp.media.model.Attachments;
   513 		return Attachments.all.push( attachment || { id: id } );
   515 		return Attachments.all.push( attachment || { id: id } );
   514 	})
   516 	})
   609 	 * the `props` values, and sync the results to this collection.
   611 	 * the `props` values, and sync the results to this collection.
   610 	 *
   612 	 *
   611 	 * @access private
   613 	 * @access private
   612 	 *
   614 	 *
   613 	 * @param {Backbone.Model} model
   615 	 * @param {Backbone.Model} model
   614 	 * @param {Boolean} query
   616 	 * @param {boolean} query
   615 	 */
   617 	 */
   616 	_changeQuery: function( model, query ) {
   618 	_changeQuery: function( model, query ) {
   617 		if ( query ) {
   619 		if ( query ) {
   618 			this.props.on( 'change', this._requery, this );
   620 			this.props.on( 'change', this._requery, this );
   619 			this._requery();
   621 			this._requery();
   655 
   657 
   656 		if ( ! changed ) {
   658 		if ( ! changed ) {
   657 			return;
   659 			return;
   658 		}
   660 		}
   659 
   661 
   660 		// If no `Attachments` model is provided to source the searches
   662 		// If no `Attachments` model is provided to source the searches from,
   661 		// from, then automatically generate a source from the existing
   663 		// then automatically generate a source from the existing models.
   662 		// models.
       
   663 		if ( ! this._source ) {
   664 		if ( ! this._source ) {
   664 			this._source = new Attachments( this.models );
   665 			this._source = new Attachments( this.models );
   665 		}
   666 		}
   666 
   667 
   667 		this.reset( this._source.filter( this.validator, this ) );
   668 		this.reset( this._source.filter( this.validator, this ) );
   670 	validateDestroyed: false,
   671 	validateDestroyed: false,
   671 	/**
   672 	/**
   672 	 * Checks whether an attachment is valid.
   673 	 * Checks whether an attachment is valid.
   673 	 *
   674 	 *
   674 	 * @param {wp.media.model.Attachment} attachment
   675 	 * @param {wp.media.model.Attachment} attachment
   675 	 * @returns {Boolean}
   676 	 * @return {boolean}
   676 	 */
   677 	 */
   677 	validator: function( attachment ) {
   678 	validator: function( attachment ) {
   678 
   679 
   679 		// Filter out contextually created attachments (e.g. headers, logos, etc.).
   680 		// Filter out contextually created attachments (e.g. headers, logos, etc.).
   680 		if (
   681 		if (
   694 	/**
   695 	/**
   695 	 * Add or remove an attachment to the collection depending on its validity.
   696 	 * Add or remove an attachment to the collection depending on its validity.
   696 	 *
   697 	 *
   697 	 * @param {wp.media.model.Attachment} attachment
   698 	 * @param {wp.media.model.Attachment} attachment
   698 	 * @param {Object} options
   699 	 * @param {Object} options
   699 	 * @returns {wp.media.model.Attachments} Returns itself to allow chaining
   700 	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
   700 	 */
   701 	 */
   701 	validate: function( attachment, options ) {
   702 	validate: function( attachment, options ) {
   702 		var valid = this.validator( attachment ),
   703 		var valid = this.validator( attachment ),
   703 			hasAttachment = !! this.get( attachment.cid );
   704 			hasAttachment = !! this.get( attachment.cid );
   704 
   705 
   713 
   714 
   714 	/**
   715 	/**
   715 	 * Add or remove all attachments from another collection depending on each one's validity.
   716 	 * Add or remove all attachments from another collection depending on each one's validity.
   716 	 *
   717 	 *
   717 	 * @param {wp.media.model.Attachments} attachments
   718 	 * @param {wp.media.model.Attachments} attachments
   718 	 * @param {object} [options={}]
   719 	 * @param {Object} [options={}]
   719 	 *
   720 	 *
   720 	 * @fires wp.media.model.Attachments#reset
   721 	 * @fires wp.media.model.Attachments#reset
   721 	 *
   722 	 *
   722 	 * @returns {wp.media.model.Attachments} Returns itself to allow chaining
   723 	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
   723 	 */
   724 	 */
   724 	validateAll: function( attachments, options ) {
   725 	validateAll: function( attachments, options ) {
   725 		options = options || {};
   726 		options = options || {};
   726 
   727 
   727 		_.each( attachments.models, function( attachment ) {
   728 		_.each( attachments.models, function( attachment ) {
   736 	/**
   737 	/**
   737 	 * Start observing another attachments collection change events
   738 	 * Start observing another attachments collection change events
   738 	 * and replicate them on this collection.
   739 	 * and replicate them on this collection.
   739 	 *
   740 	 *
   740 	 * @param {wp.media.model.Attachments} The attachments collection to observe.
   741 	 * @param {wp.media.model.Attachments} The attachments collection to observe.
   741 	 * @returns {wp.media.model.Attachments} Returns itself to allow chaining.
   742 	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
   742 	 */
   743 	 */
   743 	observe: function( attachments ) {
   744 	observe: function( attachments ) {
   744 		this.observers = this.observers || [];
   745 		this.observers = this.observers || [];
   745 		this.observers.push( attachments );
   746 		this.observers.push( attachments );
   746 
   747 
   751 	},
   752 	},
   752 	/**
   753 	/**
   753 	 * Stop replicating collection change events from another attachments collection.
   754 	 * Stop replicating collection change events from another attachments collection.
   754 	 *
   755 	 *
   755 	 * @param {wp.media.model.Attachments} The attachments collection to stop observing.
   756 	 * @param {wp.media.model.Attachments} The attachments collection to stop observing.
   756 	 * @returns {wp.media.model.Attachments} Returns itself to allow chaining
   757 	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
   757 	 */
   758 	 */
   758 	unobserve: function( attachments ) {
   759 	unobserve: function( attachments ) {
   759 		if ( attachments ) {
   760 		if ( attachments ) {
   760 			attachments.off( null, null, this );
   761 			attachments.off( null, null, this );
   761 			this.observers = _.without( this.observers, attachments );
   762 			this.observers = _.without( this.observers, attachments );
   774 	 *
   775 	 *
   775 	 * @param {wp.media.model.Attachments} attachment
   776 	 * @param {wp.media.model.Attachments} attachment
   776 	 * @param {wp.media.model.Attachments} attachments
   777 	 * @param {wp.media.model.Attachments} attachments
   777 	 * @param {Object} options
   778 	 * @param {Object} options
   778 	 *
   779 	 *
   779 	 * @returns {wp.media.model.Attachments} Returns itself to allow chaining
   780 	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
   780 	 */
   781 	 */
   781 	_validateHandler: function( attachment, attachments, options ) {
   782 	_validateHandler: function( attachment, attachments, options ) {
   782 		// If we're not mirroring this `attachments` collection,
   783 		// If we're not mirroring this `attachments` collection,
   783 		// only retain the `silent` option.
   784 		// only retain the `silent` option.
   784 		options = attachments === this.mirroring ? options : {
   785 		options = attachments === this.mirroring ? options : {
   790 	/**
   791 	/**
   791 	 * @access private
   792 	 * @access private
   792 	 *
   793 	 *
   793 	 * @param {wp.media.model.Attachments} attachments
   794 	 * @param {wp.media.model.Attachments} attachments
   794 	 * @param {Object} options
   795 	 * @param {Object} options
   795 	 * @returns {wp.media.model.Attachments} Returns itself to allow chaining
   796 	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
   796 	 */
   797 	 */
   797 	_validateAllHandler: function( attachments, options ) {
   798 	_validateAllHandler: function( attachments, options ) {
   798 		return this.validateAll( attachments, options );
   799 		return this.validateAll( attachments, options );
   799 	},
   800 	},
   800 	/**
   801 	/**
   801 	 * Start mirroring another attachments collection, clearing out any models already
   802 	 * Start mirroring another attachments collection, clearing out any models already
   802 	 * in the collection.
   803 	 * in the collection.
   803 	 *
   804 	 *
   804 	 * @param {wp.media.model.Attachments} The attachments collection to mirror.
   805 	 * @param {wp.media.model.Attachments} The attachments collection to mirror.
   805 	 * @returns {wp.media.model.Attachments} Returns itself to allow chaining
   806 	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
   806 	 */
   807 	 */
   807 	mirror: function( attachments ) {
   808 	mirror: function( attachments ) {
   808 		if ( this.mirroring && this.mirroring === attachments ) {
   809 		if ( this.mirroring && this.mirroring === attachments ) {
   809 			return this;
   810 			return this;
   810 		}
   811 		}
   815 		// Clear the collection silently. A `reset` event will be fired
   816 		// Clear the collection silently. A `reset` event will be fired
   816 		// when `observe()` calls `validateAll()`.
   817 		// when `observe()` calls `validateAll()`.
   817 		this.reset( [], { silent: true } );
   818 		this.reset( [], { silent: true } );
   818 		this.observe( attachments );
   819 		this.observe( attachments );
   819 
   820 
       
   821 		// Used for the search results.
       
   822 		this.trigger( 'attachments:received', this );
   820 		return this;
   823 		return this;
   821 	},
   824 	},
   822 	/**
   825 	/**
   823 	 * Stop mirroring another attachments collection.
   826 	 * Stop mirroring another attachments collection.
   824 	 */
   827 	 */
   835 	 *
   838 	 *
   836 	 * Only works if the collection is mirroring a Query Attachments collection,
   839 	 * Only works if the collection is mirroring a Query Attachments collection,
   837 	 * and forwards to its `more` method. This collection class doesn't have
   840 	 * and forwards to its `more` method. This collection class doesn't have
   838 	 * server persistence by itself.
   841 	 * server persistence by itself.
   839 	 *
   842 	 *
   840 	 * @param {object} options
   843 	 * @param {Object} options
   841 	 * @returns {Promise}
   844 	 * @return {Promise}
   842 	 */
   845 	 */
   843 	more: function( options ) {
   846 	more: function( options ) {
   844 		var deferred = jQuery.Deferred(),
   847 		var deferred = jQuery.Deferred(),
   845 			mirroring = this.mirroring,
   848 			mirroring = this.mirroring,
   846 			attachments = this;
   849 			attachments = this;
   847 
   850 
   848 		if ( ! mirroring || ! mirroring.more ) {
   851 		if ( ! mirroring || ! mirroring.more ) {
   849 			return deferred.resolveWith( this ).promise();
   852 			return deferred.resolveWith( this ).promise();
   850 		}
   853 		}
   851 		// If we're mirroring another collection, forward `more` to
   854 		/*
   852 		// the mirrored collection. Account for a race condition by
   855 		 * If we're mirroring another collection, forward `more` to
   853 		// checking if we're still mirroring that collection when
   856 		 * the mirrored collection. Account for a race condition by
   854 		// the request resolves.
   857 		 * checking if we're still mirroring that collection when
       
   858 		 * the request resolves.
       
   859 		 */
   855 		mirroring.more( options ).done( function() {
   860 		mirroring.more( options ).done( function() {
   856 			if ( this === attachments.mirroring ) {
   861 			if ( this === attachments.mirroring ) {
   857 				deferred.resolveWith( this );
   862 				deferred.resolveWith( this );
   858 			}
   863 			}
       
   864 
       
   865 			// Used for the search results.
       
   866 			attachments.trigger( 'attachments:received', this );
   859 		});
   867 		});
   860 
   868 
   861 		return deferred.promise();
   869 		return deferred.promise();
   862 	},
   870 	},
   863 	/**
   871 	/**
   866 	 *
   874 	 *
   867 	 * Only works if the collection is mirroring a Query Attachments collection,
   875 	 * Only works if the collection is mirroring a Query Attachments collection,
   868 	 * and forwards to its `hasMore` method. This collection class doesn't have
   876 	 * and forwards to its `hasMore` method. This collection class doesn't have
   869 	 * server persistence by itself.
   877 	 * server persistence by itself.
   870 	 *
   878 	 *
   871 	 * @returns {boolean}
   879 	 * @return {boolean}
   872 	 */
   880 	 */
   873 	hasMore: function() {
   881 	hasMore: function() {
   874 		return this.mirroring ? this.mirroring.hasMore() : false;
   882 		return this.mirroring ? this.mirroring.hasMore() : false;
   875 	},
   883 	},
   876 	/**
   884 	/**
   877 	 * A custom AJAX-response parser.
   885 	 * A custom Ajax-response parser.
   878 	 *
   886 	 *
   879 	 * See trac ticket #24753
   887 	 * See trac ticket #24753
   880 	 *
   888 	 *
   881 	 * @param {Object|Array} resp The raw response Object/Array.
   889 	 * @param {Object|Array} resp The raw response Object/Array.
   882 	 * @param {Object} xhr
   890 	 * @param {Object} xhr
   883 	 * @returns {Array} The array of model attributes to be added to the collection
   891 	 * @return {Array} The array of model attributes to be added to the collection
   884 	 */
   892 	 */
   885 	parse: function( resp, xhr ) {
   893 	parse: function( resp, xhr ) {
   886 		if ( ! _.isArray( resp ) ) {
   894 		if ( ! _.isArray( resp ) ) {
   887 			resp = [resp];
   895 			resp = [resp];
   888 		}
   896 		}
   922 	},
   930 	},
   923 	/**
   931 	/**
   924 	 * If this collection is sorted by `menuOrder`, recalculates and saves
   932 	 * If this collection is sorted by `menuOrder`, recalculates and saves
   925 	 * the menu order to the database.
   933 	 * the menu order to the database.
   926 	 *
   934 	 *
   927 	 * @returns {undefined|Promise}
   935 	 * @return {undefined|Promise}
   928 	 */
   936 	 */
   929 	saveMenuOrder: function() {
   937 	saveMenuOrder: function() {
   930 		if ( 'menuOrder' !== this.props.get('orderby') ) {
   938 		if ( 'menuOrder' !== this.props.get('orderby') ) {
   931 			return;
   939 			return;
   932 		}
   940 		}
   933 
   941 
   934 		// Removes any uploading attachments, updates each attachment's
   942 		/*
   935 		// menu order, and returns an object with an { id: menuOrder }
   943 		 * Removes any uploading attachments, updates each attachment's
   936 		// mapping to pass to the request.
   944 		 * menu order, and returns an object with an { id: menuOrder }
       
   945 		 * mapping to pass to the request.
       
   946 		 */
   937 		var attachments = this.chain().filter( function( attachment ) {
   947 		var attachments = this.chain().filter( function( attachment ) {
   938 			return ! _.isUndefined( attachment.id );
   948 			return ! _.isUndefined( attachment.id );
   939 		}).map( function( attachment, index ) {
   949 		}).map( function( attachment, index ) {
   940 			// Indices start at 1.
   950 			// Indices start at 1.
   941 			index = index + 1;
   951 			index = index + 1;
   961 	 * and its subclasses. @see wp.media.model.Attachments._changeOrderby().
   971 	 * and its subclasses. @see wp.media.model.Attachments._changeOrderby().
   962 	 *
   972 	 *
   963 	 * @param {Backbone.Model} a
   973 	 * @param {Backbone.Model} a
   964 	 * @param {Backbone.Model} b
   974 	 * @param {Backbone.Model} b
   965 	 * @param {Object} options
   975 	 * @param {Object} options
   966 	 * @returns {Number} -1 if the first model should come before the second,
   976 	 * @return {number} -1 if the first model should come before the second,
   967 	 *    0 if they are of the same rank and
   977 	 *                   0 if they are of the same rank and
   968 	 *    1 if the first model should come after.
   978 	 *                   1 if the first model should come after.
   969 	 */
   979 	 */
   970 	comparator: function( a, b, options ) {
   980 	comparator: function( a, b, options ) {
   971 		var key   = this.props.get('orderby'),
   981 		var key   = this.props.get('orderby'),
   972 			order = this.props.get('order') || 'DESC',
   982 			order = this.props.get('order') || 'DESC',
   973 			ac    = a.cid,
   983 			ac    = a.cid,
   997 		 *
  1007 		 *
   998 		 * @param {wp.media.model.Attachment} attachment
  1008 		 * @param {wp.media.model.Attachment} attachment
   999 		 *
  1009 		 *
  1000 		 * @this wp.media.model.Attachments
  1010 		 * @this wp.media.model.Attachments
  1001 		 *
  1011 		 *
  1002 		 * @returns {Boolean}
  1012 		 * @return {Boolean}
  1003 		 */
  1013 		 */
  1004 		search: function( attachment ) {
  1014 		search: function( attachment ) {
  1005 			if ( ! this.props.get('search') ) {
  1015 			if ( ! this.props.get('search') ) {
  1006 				return true;
  1016 				return true;
  1007 			}
  1017 			}
  1015 		 * @static
  1025 		 * @static
  1016 		 * @param {wp.media.model.Attachment} attachment
  1026 		 * @param {wp.media.model.Attachment} attachment
  1017 		 *
  1027 		 *
  1018 		 * @this wp.media.model.Attachments
  1028 		 * @this wp.media.model.Attachments
  1019 		 *
  1029 		 *
  1020 		 * @returns {Boolean}
  1030 		 * @return {boolean}
  1021 		 */
  1031 		 */
  1022 		type: function( attachment ) {
  1032 		type: function( attachment ) {
  1023 			var type = this.props.get('type'), atts = attachment.toJSON(), mime, found;
  1033 			var type = this.props.get('type'), atts = attachment.toJSON(), mime, found;
  1024 
  1034 
  1025 			if ( ! type || ( _.isArray( type ) && ! type.length ) ) {
  1035 			if ( ! type || ( _.isArray( type ) && ! type.length ) ) {
  1042 		 * @static
  1052 		 * @static
  1043 		 * @param {wp.media.model.Attachment} attachment
  1053 		 * @param {wp.media.model.Attachment} attachment
  1044 		 *
  1054 		 *
  1045 		 * @this wp.media.model.Attachments
  1055 		 * @this wp.media.model.Attachments
  1046 		 *
  1056 		 *
  1047 		 * @returns {Boolean}
  1057 		 * @return {boolean}
  1048 		 */
  1058 		 */
  1049 		uploadedTo: function( attachment ) {
  1059 		uploadedTo: function( attachment ) {
  1050 			var uploadedTo = this.props.get('uploadedTo');
  1060 			var uploadedTo = this.props.get('uploadedTo');
  1051 			if ( _.isUndefined( uploadedTo ) ) {
  1061 			if ( _.isUndefined( uploadedTo ) ) {
  1052 				return true;
  1062 				return true;
  1058 		 * @static
  1068 		 * @static
  1059 		 * @param {wp.media.model.Attachment} attachment
  1069 		 * @param {wp.media.model.Attachment} attachment
  1060 		 *
  1070 		 *
  1061 		 * @this wp.media.model.Attachments
  1071 		 * @this wp.media.model.Attachments
  1062 		 *
  1072 		 *
  1063 		 * @returns {Boolean}
  1073 		 * @return {boolean}
  1064 		 */
  1074 		 */
  1065 		status: function( attachment ) {
  1075 		status: function( attachment ) {
  1066 			var status = this.props.get('status');
  1076 			var status = this.props.get('status');
  1067 			if ( _.isUndefined( status ) ) {
  1077 			if ( _.isUndefined( status ) ) {
  1068 				return true;
  1078 				return true;
  1103  * @param {object} [options.args]                Attachments query arguments.
  1113  * @param {object} [options.args]                Attachments query arguments.
  1104  * @param {object} [options.args.posts_per_page]
  1114  * @param {object} [options.args.posts_per_page]
  1105  */
  1115  */
  1106 Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{
  1116 Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{
  1107 	/**
  1117 	/**
  1108 	 * @param {array}  [models=[]]  Array of initial models to populate the collection.
  1118 	 * @param {Array}  [models=[]]  Array of initial models to populate the collection.
  1109 	 * @param {object} [options={}]
  1119 	 * @param {Object} [options={}]
  1110 	 */
  1120 	 */
  1111 	initialize: function( models, options ) {
  1121 	initialize: function( models, options ) {
  1112 		var allowed;
  1122 		var allowed;
  1113 
  1123 
  1114 		options = options || {};
  1124 		options = options || {};
  1124 
  1134 
  1125 			if ( ! this.comparator ) {
  1135 			if ( ! this.comparator ) {
  1126 				return true;
  1136 				return true;
  1127 			}
  1137 			}
  1128 
  1138 
  1129 			// We want any items that can be placed before the last
  1139 			/*
  1130 			// item in the set. If we add any items after the last
  1140 			 * We want any items that can be placed before the last
  1131 			// item, then we can't guarantee the set is complete.
  1141 			 * item in the set. If we add any items after the last
       
  1142 			 * item, then we can't guarantee the set is complete.
       
  1143 			 */
  1132 			if ( this.length ) {
  1144 			if ( this.length ) {
  1133 				return 1 !== this.comparator( attachment, this.last(), { ties: true });
  1145 				return 1 !== this.comparator( attachment, this.last(), { ties: true });
  1134 
  1146 
  1135 			// Handle the case where there are no items yet and
  1147 			/*
  1136 			// we're sorting for recent items. In that case, we want
  1148 			 * Handle the case where there are no items yet and
  1137 			// changes that occurred after we created the query.
  1149 			 * we're sorting for recent items. In that case, we want
       
  1150 			 * changes that occurred after we created the query.
       
  1151 			 */
  1138 			} else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
  1152 			} else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
  1139 				return attachment.get( orderby ) >= this.created;
  1153 				return attachment.get( orderby ) >= this.created;
  1140 
  1154 
  1141 			// If we're sorting by menu order and we have no items,
  1155 			// If we're sorting by menu order and we have no items,
  1142 			// accept any items that have the default menu order (0).
  1156 			// accept any items that have the default menu order (0).
  1146 
  1160 
  1147 			// Otherwise, we don't want any items yet.
  1161 			// Otherwise, we don't want any items yet.
  1148 			return false;
  1162 			return false;
  1149 		};
  1163 		};
  1150 
  1164 
  1151 		// Observe the central `wp.Uploader.queue` collection to watch for
  1165 		/*
  1152 		// new matches for the query.
  1166 		 * Observe the central `wp.Uploader.queue` collection to watch for
  1153 		//
  1167 		 * new matches for the query.
  1154 		// Only observe when a limited number of query args are set. There
  1168 		 *
  1155 		// are no filters for other properties, so observing will result in
  1169 		 * Only observe when a limited number of query args are set. There
  1156 		// false positives in those queries.
  1170 		 * are no filters for other properties, so observing will result in
       
  1171 		 * false positives in those queries.
       
  1172 		 */
  1157 		allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent', 'author' ];
  1173 		allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent', 'author' ];
  1158 		if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
  1174 		if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
  1159 			this.observe( wp.Uploader.queue );
  1175 			this.observe( wp.Uploader.queue );
  1160 		}
  1176 		}
  1161 	},
  1177 	},
  1162 	/**
  1178 	/**
  1163 	 * Whether there are more attachments that haven't been sync'd from the server
  1179 	 * Whether there are more attachments that haven't been sync'd from the server
  1164 	 * that match the collection's query.
  1180 	 * that match the collection's query.
  1165 	 *
  1181 	 *
  1166 	 * @returns {boolean}
  1182 	 * @return {boolean}
  1167 	 */
  1183 	 */
  1168 	hasMore: function() {
  1184 	hasMore: function() {
  1169 		return this._hasMore;
  1185 		return this._hasMore;
  1170 	},
  1186 	},
  1171 	/**
  1187 	/**
  1172 	 * Fetch more attachments from the server for the collection.
  1188 	 * Fetch more attachments from the server for the collection.
  1173 	 *
  1189 	 *
  1174 	 * @param   {object}  [options={}]
  1190 	 * @param {Object} [options={}]
  1175 	 * @returns {Promise}
  1191 	 * @return {Promise}
  1176 	 */
  1192 	 */
  1177 	more: function( options ) {
  1193 	more: function( options ) {
  1178 		var query = this;
  1194 		var query = this;
  1179 
  1195 
  1180 		// If there is already a request pending, return early with the Deferred object.
  1196 		// If there is already a request pending, return early with the Deferred object.
  1197 	},
  1213 	},
  1198 	/**
  1214 	/**
  1199 	 * Overrides Backbone.Collection.sync
  1215 	 * Overrides Backbone.Collection.sync
  1200 	 * Overrides wp.media.model.Attachments.sync
  1216 	 * Overrides wp.media.model.Attachments.sync
  1201 	 *
  1217 	 *
  1202 	 * @param {String} method
  1218 	 * @param {string} method
  1203 	 * @param {Backbone.Model} model
  1219 	 * @param {Backbone.Model} model
  1204 	 * @param {Object} [options={}]
  1220 	 * @param {Object} [options={}]
  1205 	 * @returns {Promise}
  1221 	 * @return {Promise}
  1206 	 */
  1222 	 */
  1207 	sync: function( method, model, options ) {
  1223 	sync: function( method, model, options ) {
  1208 		var args, fallback;
  1224 		var args, fallback;
  1209 
  1225 
  1210 		// Overload the read method so Attachment.fetch() functions correctly.
  1226 		// Overload the read method so Attachment.fetch() functions correctly.
  1225 			}
  1241 			}
  1226 
  1242 
  1227 			options.data.query = args;
  1243 			options.data.query = args;
  1228 			return wp.media.ajax( options );
  1244 			return wp.media.ajax( options );
  1229 
  1245 
  1230 		// Otherwise, fall back to Backbone.sync()
  1246 		// Otherwise, fall back to `Backbone.sync()`.
  1231 		} else {
  1247 		} else {
  1232 			/**
  1248 			/**
  1233 			 * Call wp.media.model.Attachments.sync or Backbone.sync
  1249 			 * Call wp.media.model.Attachments.sync or Backbone.sync
  1234 			 */
  1250 			 */
  1235 			fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
  1251 			fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
  1302 	 * @param {Object} [props.post_parent]
  1318 	 * @param {Object} [props.post_parent]
  1303 	 * @param {Object} [props.post_status]
  1319 	 * @param {Object} [props.post_status]
  1304 	 * @param {Object} [props.author]
  1320 	 * @param {Object} [props.author]
  1305 	 * @param {Object} [options]
  1321 	 * @param {Object} [options]
  1306 	 *
  1322 	 *
  1307 	 * @returns {wp.media.model.Query} A new Attachments Query collection.
  1323 	 * @return {wp.media.model.Query} A new Attachments Query collection.
  1308 	 */
  1324 	 */
  1309 	get: (function(){
  1325 	get: (function(){
  1310 		/**
  1326 		/**
  1311 		 * @static
  1327 		 * @static
  1312 		 * @type Array
  1328 		 * @type Array
  1313 		 */
  1329 		 */
  1314 		var queries = [];
  1330 		var queries = [];
  1315 
  1331 
  1316 		/**
  1332 		/**
  1317 		 * @returns {Query}
  1333 		 * @return {Query}
  1318 		 */
  1334 		 */
  1319 		return function( props, options ) {
  1335 		return function( props, options ) {
  1320 			var args     = {},
  1336 			var args     = {},
  1321 				orderby  = Query.orderby,
  1337 				orderby  = Query.orderby,
  1322 				defaults = Query.defaultProps,
  1338 				defaults = Query.defaultProps,
  1426 				this.dfd = this.attachment.fetch();
  1442 				this.dfd = this.attachment.fetch();
  1427 			}
  1443 			}
  1428 			this.bindAttachmentListeners();
  1444 			this.bindAttachmentListeners();
  1429 		}
  1445 		}
  1430 
  1446 
  1431 		// keep url in sync with changes to the type of link
  1447 		// Keep URL in sync with changes to the type of link.
  1432 		this.on( 'change:link', this.updateLinkUrl, this );
  1448 		this.on( 'change:link', this.updateLinkUrl, this );
  1433 		this.on( 'change:size', this.updateSize, this );
  1449 		this.on( 'change:size', this.updateSize, this );
  1434 
  1450 
  1435 		this.setLinkTypeFromUrl();
  1451 		this.setLinkTypeFromUrl();
  1436 		this.setAspectRatio();
  1452 		this.setAspectRatio();
  1466 		if ( ! linkUrl ) {
  1482 		if ( ! linkUrl ) {
  1467 			this.set( 'link', 'none' );
  1483 			this.set( 'link', 'none' );
  1468 			return;
  1484 			return;
  1469 		}
  1485 		}
  1470 
  1486 
  1471 		// default to custom if there is a linkUrl
  1487 		// Default to custom if there is a linkUrl.
  1472 		type = 'custom';
  1488 		type = 'custom';
  1473 
  1489 
  1474 		if ( this.attachment ) {
  1490 		if ( this.attachment ) {
  1475 			if ( this.attachment.get( 'url' ) === linkUrl ) {
  1491 			if ( this.attachment.get( 'url' ) === linkUrl ) {
  1476 				type = 'file';
  1492 				type = 'file';
  1594 	 * If the workflow does not support multi-select, clear out the selection
  1610 	 * If the workflow does not support multi-select, clear out the selection
  1595 	 * before adding a new attachment to it.
  1611 	 * before adding a new attachment to it.
  1596 	 *
  1612 	 *
  1597 	 * @param {Array} models
  1613 	 * @param {Array} models
  1598 	 * @param {Object} options
  1614 	 * @param {Object} options
  1599 	 * @returns {wp.media.model.Attachment[]}
  1615 	 * @return {wp.media.model.Attachment[]}
  1600 	 */
  1616 	 */
  1601 	add: function( models, options ) {
  1617 	add: function( models, options ) {
  1602 		if ( ! this.multiple ) {
  1618 		if ( ! this.multiple ) {
  1603 			this.remove( this.models );
  1619 			this.remove( this.models );
  1604 		}
  1620 		}
  1614 	 * @param {undefined|boolean|wp.media.model.Attachment} model
  1630 	 * @param {undefined|boolean|wp.media.model.Attachment} model
  1615 	 *
  1631 	 *
  1616 	 * @fires wp.media.model.Selection#selection:single
  1632 	 * @fires wp.media.model.Selection#selection:single
  1617 	 * @fires wp.media.model.Selection#selection:unsingle
  1633 	 * @fires wp.media.model.Selection#selection:unsingle
  1618 	 *
  1634 	 *
  1619 	 * @returns {Backbone.Model}
  1635 	 * @return {Backbone.Model}
  1620 	 */
  1636 	 */
  1621 	single: function( model ) {
  1637 	single: function( model ) {
  1622 		var previous = this._single;
  1638 		var previous = this._single;
  1623 
  1639 
  1624 		// If a `model` is provided, use it as the single model.
  1640 		// If a `model` is provided, use it as the single model.