]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/media-models.js
WordPress 4.2.3-scripts
[autoinstalls/wordpress.git] / wp-includes / js / media-models.js
1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2 /*globals wp, _, jQuery */
3
4 var $ = jQuery,
5         Attachment, Attachments, l10n, media;
6
7 window.wp = window.wp || {};
8
9 /**
10  * Create and return a media frame.
11  *
12  * Handles the default media experience.
13  *
14  * @param  {object} attributes The properties passed to the main media controller.
15  * @return {wp.media.view.MediaFrame} A media workflow.
16  */
17 media = wp.media = function( attributes ) {
18         var MediaFrame = media.view.MediaFrame,
19                 frame;
20
21         if ( ! MediaFrame ) {
22                 return;
23         }
24
25         attributes = _.defaults( attributes || {}, {
26                 frame: 'select'
27         });
28
29         if ( 'select' === attributes.frame && MediaFrame.Select ) {
30                 frame = new MediaFrame.Select( attributes );
31         } else if ( 'post' === attributes.frame && MediaFrame.Post ) {
32                 frame = new MediaFrame.Post( attributes );
33         } else if ( 'manage' === attributes.frame && MediaFrame.Manage ) {
34                 frame = new MediaFrame.Manage( attributes );
35         } else if ( 'image' === attributes.frame && MediaFrame.ImageDetails ) {
36                 frame = new MediaFrame.ImageDetails( attributes );
37         } else if ( 'audio' === attributes.frame && MediaFrame.AudioDetails ) {
38                 frame = new MediaFrame.AudioDetails( attributes );
39         } else if ( 'video' === attributes.frame && MediaFrame.VideoDetails ) {
40                 frame = new MediaFrame.VideoDetails( attributes );
41         } else if ( 'edit-attachments' === attributes.frame && MediaFrame.EditAttachments ) {
42                 frame = new MediaFrame.EditAttachments( attributes );
43         }
44
45         delete attributes.frame;
46
47         media.frame = frame;
48
49         return frame;
50 };
51
52 _.extend( media, { model: {}, view: {}, controller: {}, frames: {} });
53
54 // Link any localized strings.
55 l10n = media.model.l10n = window._wpMediaModelsL10n || {};
56
57 // Link any settings.
58 media.model.settings = l10n.settings || {};
59 delete l10n.settings;
60
61 Attachment = media.model.Attachment = require( './models/attachment.js' );
62 Attachments = media.model.Attachments = require( './models/attachments.js' );
63
64 media.model.Query = require( './models/query.js' );
65 media.model.PostImage = require( './models/post-image.js' );
66 media.model.Selection = require( './models/selection.js' );
67
68 /**
69  * ========================================================================
70  * UTILITIES
71  * ========================================================================
72  */
73
74 /**
75  * A basic equality comparator for Backbone models.
76  *
77  * Used to order models within a collection - @see wp.media.model.Attachments.comparator().
78  *
79  * @param  {mixed}  a  The primary parameter to compare.
80  * @param  {mixed}  b  The primary parameter to compare.
81  * @param  {string} ac The fallback parameter to compare, a's cid.
82  * @param  {string} bc The fallback parameter to compare, b's cid.
83  * @return {number}    -1: a should come before b.
84  *                      0: a and b are of the same rank.
85  *                      1: b should come before a.
86  */
87 media.compare = function( a, b, ac, bc ) {
88         if ( _.isEqual( a, b ) ) {
89                 return ac === bc ? 0 : (ac > bc ? -1 : 1);
90         } else {
91                 return a > b ? -1 : 1;
92         }
93 };
94
95 _.extend( media, {
96         /**
97          * media.template( id )
98          *
99          * Fetch a JavaScript template for an id, and return a templating function for it.
100          *
101          * See wp.template() in `wp-includes/js/wp-util.js`.
102          *
103          * @borrows wp.template as template
104          */
105         template: wp.template,
106
107         /**
108          * media.post( [action], [data] )
109          *
110          * Sends a POST request to WordPress.
111          * See wp.ajax.post() in `wp-includes/js/wp-util.js`.
112          *
113          * @borrows wp.ajax.post as post
114          */
115         post: wp.ajax.post,
116
117         /**
118          * media.ajax( [action], [options] )
119          *
120          * Sends an XHR request to WordPress.
121          * See wp.ajax.send() in `wp-includes/js/wp-util.js`.
122          *
123          * @borrows wp.ajax.send as ajax
124          */
125         ajax: wp.ajax.send,
126
127         /**
128          * Scales a set of dimensions to fit within bounding dimensions.
129          *
130          * @param {Object} dimensions
131          * @returns {Object}
132          */
133         fit: function( dimensions ) {
134                 var width     = dimensions.width,
135                         height    = dimensions.height,
136                         maxWidth  = dimensions.maxWidth,
137                         maxHeight = dimensions.maxHeight,
138                         constraint;
139
140                 // Compare ratios between the two values to determine which
141                 // max to constrain by. If a max value doesn't exist, then the
142                 // opposite side is the constraint.
143                 if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) {
144                         constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height';
145                 } else if ( _.isUndefined( maxHeight ) ) {
146                         constraint = 'width';
147                 } else if (  _.isUndefined( maxWidth ) && height > maxHeight ) {
148                         constraint = 'height';
149                 }
150
151                 // If the value of the constrained side is larger than the max,
152                 // then scale the values. Otherwise return the originals; they fit.
153                 if ( 'width' === constraint && width > maxWidth ) {
154                         return {
155                                 width : maxWidth,
156                                 height: Math.round( maxWidth * height / width )
157                         };
158                 } else if ( 'height' === constraint && height > maxHeight ) {
159                         return {
160                                 width : Math.round( maxHeight * width / height ),
161                                 height: maxHeight
162                         };
163                 } else {
164                         return {
165                                 width : width,
166                                 height: height
167                         };
168                 }
169         },
170         /**
171          * Truncates a string by injecting an ellipsis into the middle.
172          * Useful for filenames.
173          *
174          * @param {String} string
175          * @param {Number} [length=30]
176          * @param {String} [replacement=&hellip;]
177          * @returns {String} The string, unless length is greater than string.length.
178          */
179         truncate: function( string, length, replacement ) {
180                 length = length || 30;
181                 replacement = replacement || '&hellip;';
182
183                 if ( string.length <= length ) {
184                         return string;
185                 }
186
187                 return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 );
188         }
189 });
190
191 /**
192  * ========================================================================
193  * MODELS
194  * ========================================================================
195  */
196 /**
197  * wp.media.attachment
198  *
199  * @static
200  * @param {String} id A string used to identify a model.
201  * @returns {wp.media.model.Attachment}
202  */
203 media.attachment = function( id ) {
204         return Attachment.get( id );
205 };
206
207 /**
208  * A collection of all attachments that have been fetched from the server.
209  *
210  * @static
211  * @member {wp.media.model.Attachments}
212  */
213 Attachments.all = new Attachments();
214
215 /**
216  * wp.media.query
217  *
218  * Shorthand for creating a new Attachments Query.
219  *
220  * @param {object} [props]
221  * @returns {wp.media.model.Attachments}
222  */
223 media.query = function( props ) {
224         return new Attachments( null, {
225                 props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
226         });
227 };
228
229 // Clean up. Prevents mobile browsers caching
230 $(window).on('unload', function(){
231         window.wp = null;
232 });
233
234 },{"./models/attachment.js":2,"./models/attachments.js":3,"./models/post-image.js":4,"./models/query.js":5,"./models/selection.js":6}],2:[function(require,module,exports){
235 /*globals wp, _, Backbone */
236
237 /**
238  * wp.media.model.Attachment
239  *
240  * @class
241  * @augments Backbone.Model
242  */
243 var $ = Backbone.$,
244         Attachment;
245
246 Attachment = Backbone.Model.extend({
247         /**
248          * Triggered when attachment details change
249          * Overrides Backbone.Model.sync
250          *
251          * @param {string} method
252          * @param {wp.media.model.Attachment} model
253          * @param {Object} [options={}]
254          *
255          * @returns {Promise}
256          */
257         sync: function( method, model, options ) {
258                 // If the attachment does not yet have an `id`, return an instantly
259                 // rejected promise. Otherwise, all of our requests will fail.
260                 if ( _.isUndefined( this.id ) ) {
261                         return $.Deferred().rejectWith( this ).promise();
262                 }
263
264                 // Overload the `read` request so Attachment.fetch() functions correctly.
265                 if ( 'read' === method ) {
266                         options = options || {};
267                         options.context = this;
268                         options.data = _.extend( options.data || {}, {
269                                 action: 'get-attachment',
270                                 id: this.id
271                         });
272                         return wp.media.ajax( options );
273
274                 // Overload the `update` request so properties can be saved.
275                 } else if ( 'update' === method ) {
276                         // If we do not have the necessary nonce, fail immeditately.
277                         if ( ! this.get('nonces') || ! this.get('nonces').update ) {
278                                 return $.Deferred().rejectWith( this ).promise();
279                         }
280
281                         options = options || {};
282                         options.context = this;
283
284                         // Set the action and ID.
285                         options.data = _.extend( options.data || {}, {
286                                 action:  'save-attachment',
287                                 id:      this.id,
288                                 nonce:   this.get('nonces').update,
289                                 post_id: wp.media.model.settings.post.id
290                         });
291
292                         // Record the values of the changed attributes.
293                         if ( model.hasChanged() ) {
294                                 options.data.changes = {};
295
296                                 _.each( model.changed, function( value, key ) {
297                                         options.data.changes[ key ] = this.get( key );
298                                 }, this );
299                         }
300
301                         return wp.media.ajax( options );
302
303                 // Overload the `delete` request so attachments can be removed.
304                 // This will permanently delete an attachment.
305                 } else if ( 'delete' === method ) {
306                         options = options || {};
307
308                         if ( ! options.wait ) {
309                                 this.destroyed = true;
310                         }
311
312                         options.context = this;
313                         options.data = _.extend( options.data || {}, {
314                                 action:   'delete-post',
315                                 id:       this.id,
316                                 _wpnonce: this.get('nonces')['delete']
317                         });
318
319                         return wp.media.ajax( options ).done( function() {
320                                 this.destroyed = true;
321                         }).fail( function() {
322                                 this.destroyed = false;
323                         });
324
325                 // Otherwise, fall back to `Backbone.sync()`.
326                 } else {
327                         /**
328                          * Call `sync` directly on Backbone.Model
329                          */
330                         return Backbone.Model.prototype.sync.apply( this, arguments );
331                 }
332         },
333         /**
334          * Convert date strings into Date objects.
335          *
336          * @param {Object} resp The raw response object, typically returned by fetch()
337          * @returns {Object} The modified response object, which is the attributes hash
338          *    to be set on the model.
339          */
340         parse: function( resp ) {
341                 if ( ! resp ) {
342                         return resp;
343                 }
344
345                 resp.date = new Date( resp.date );
346                 resp.modified = new Date( resp.modified );
347                 return resp;
348         },
349         /**
350          * @param {Object} data The properties to be saved.
351          * @param {Object} options Sync options. e.g. patch, wait, success, error.
352          *
353          * @this Backbone.Model
354          *
355          * @returns {Promise}
356          */
357         saveCompat: function( data, options ) {
358                 var model = this;
359
360                 // If we do not have the necessary nonce, fail immeditately.
361                 if ( ! this.get('nonces') || ! this.get('nonces').update ) {
362                         return $.Deferred().rejectWith( this ).promise();
363                 }
364
365                 return wp.media.post( 'save-attachment-compat', _.defaults({
366                         id:      this.id,
367                         nonce:   this.get('nonces').update,
368                         post_id: wp.media.model.settings.post.id
369                 }, data ) ).done( function( resp, status, xhr ) {
370                         model.set( model.parse( resp, xhr ), options );
371                 });
372         }
373 }, {
374         /**
375          * Create a new model on the static 'all' attachments collection and return it.
376          *
377          * @static
378          * @param {Object} attrs
379          * @returns {wp.media.model.Attachment}
380          */
381         create: function( attrs ) {
382                 var Attachments = wp.media.model.Attachments;
383                 return Attachments.all.push( attrs );
384         },
385         /**
386          * Create a new model on the static 'all' attachments collection and return it.
387          *
388          * If this function has already been called for the id,
389          * it returns the specified attachment.
390          *
391          * @static
392          * @param {string} id A string used to identify a model.
393          * @param {Backbone.Model|undefined} attachment
394          * @returns {wp.media.model.Attachment}
395          */
396         get: _.memoize( function( id, attachment ) {
397                 var Attachments = wp.media.model.Attachments;
398                 return Attachments.all.push( attachment || { id: id } );
399         })
400 });
401
402 module.exports = Attachment;
403
404 },{}],3:[function(require,module,exports){
405 /*globals wp, _, Backbone */
406
407 /**
408  * wp.media.model.Attachments
409  *
410  * A collection of attachments.
411  *
412  * This collection has no persistence with the server without supplying
413  * 'options.props.query = true', which will mirror the collection
414  * to an Attachments Query collection - @see wp.media.model.Attachments.mirror().
415  *
416  * @class
417  * @augments Backbone.Collection
418  *
419  * @param {array}  [models]                Models to initialize with the collection.
420  * @param {object} [options]               Options hash for the collection.
421  * @param {string} [options.props]         Options hash for the initial query properties.
422  * @param {string} [options.props.order]   Initial order (ASC or DESC) for the collection.
423  * @param {string} [options.props.orderby] Initial attribute key to order the collection by.
424  * @param {string} [options.props.query]   Whether the collection is linked to an attachments query.
425  * @param {string} [options.observe]
426  * @param {string} [options.filters]
427  *
428  */
429 var Attachments = Backbone.Collection.extend({
430         /**
431          * @type {wp.media.model.Attachment}
432          */
433         model: wp.media.model.Attachment,
434         /**
435          * @param {Array} [models=[]] Array of models used to populate the collection.
436          * @param {Object} [options={}]
437          */
438         initialize: function( models, options ) {
439                 options = options || {};
440
441                 this.props   = new Backbone.Model();
442                 this.filters = options.filters || {};
443
444                 // Bind default `change` events to the `props` model.
445                 this.props.on( 'change', this._changeFilteredProps, this );
446
447                 this.props.on( 'change:order',   this._changeOrder,   this );
448                 this.props.on( 'change:orderby', this._changeOrderby, this );
449                 this.props.on( 'change:query',   this._changeQuery,   this );
450
451                 this.props.set( _.defaults( options.props || {} ) );
452
453                 if ( options.observe ) {
454                         this.observe( options.observe );
455                 }
456         },
457         /**
458          * Sort the collection when the order attribute changes.
459          *
460          * @access private
461          */
462         _changeOrder: function() {
463                 if ( this.comparator ) {
464                         this.sort();
465                 }
466         },
467         /**
468          * Set the default comparator only when the `orderby` property is set.
469          *
470          * @access private
471          *
472          * @param {Backbone.Model} model
473          * @param {string} orderby
474          */
475         _changeOrderby: function( model, orderby ) {
476                 // If a different comparator is defined, bail.
477                 if ( this.comparator && this.comparator !== Attachments.comparator ) {
478                         return;
479                 }
480
481                 if ( orderby && 'post__in' !== orderby ) {
482                         this.comparator = Attachments.comparator;
483                 } else {
484                         delete this.comparator;
485                 }
486         },
487         /**
488          * If the `query` property is set to true, query the server using
489          * the `props` values, and sync the results to this collection.
490          *
491          * @access private
492          *
493          * @param {Backbone.Model} model
494          * @param {Boolean} query
495          */
496         _changeQuery: function( model, query ) {
497                 if ( query ) {
498                         this.props.on( 'change', this._requery, this );
499                         this._requery();
500                 } else {
501                         this.props.off( 'change', this._requery, this );
502                 }
503         },
504         /**
505          * @access private
506          *
507          * @param {Backbone.Model} model
508          */
509         _changeFilteredProps: function( model ) {
510                 // If this is a query, updating the collection will be handled by
511                 // `this._requery()`.
512                 if ( this.props.get('query') ) {
513                         return;
514                 }
515
516                 var changed = _.chain( model.changed ).map( function( t, prop ) {
517                         var filter = Attachments.filters[ prop ],
518                                 term = model.get( prop );
519
520                         if ( ! filter ) {
521                                 return;
522                         }
523
524                         if ( term && ! this.filters[ prop ] ) {
525                                 this.filters[ prop ] = filter;
526                         } else if ( ! term && this.filters[ prop ] === filter ) {
527                                 delete this.filters[ prop ];
528                         } else {
529                                 return;
530                         }
531
532                         // Record the change.
533                         return true;
534                 }, this ).any().value();
535
536                 if ( ! changed ) {
537                         return;
538                 }
539
540                 // If no `Attachments` model is provided to source the searches
541                 // from, then automatically generate a source from the existing
542                 // models.
543                 if ( ! this._source ) {
544                         this._source = new Attachments( this.models );
545                 }
546
547                 this.reset( this._source.filter( this.validator, this ) );
548         },
549
550         validateDestroyed: false,
551         /**
552          * Checks whether an attachment is valid.
553          *
554          * @param {wp.media.model.Attachment} attachment
555          * @returns {Boolean}
556          */
557         validator: function( attachment ) {
558                 if ( ! this.validateDestroyed && attachment.destroyed ) {
559                         return false;
560                 }
561                 return _.all( this.filters, function( filter ) {
562                         return !! filter.call( this, attachment );
563                 }, this );
564         },
565         /**
566          * Add or remove an attachment to the collection depending on its validity.
567          *
568          * @param {wp.media.model.Attachment} attachment
569          * @param {Object} options
570          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
571          */
572         validate: function( attachment, options ) {
573                 var valid = this.validator( attachment ),
574                         hasAttachment = !! this.get( attachment.cid );
575
576                 if ( ! valid && hasAttachment ) {
577                         this.remove( attachment, options );
578                 } else if ( valid && ! hasAttachment ) {
579                         this.add( attachment, options );
580                 }
581
582                 return this;
583         },
584
585         /**
586          * Add or remove all attachments from another collection depending on each one's validity.
587          *
588          * @param {wp.media.model.Attachments} attachments
589          * @param {object} [options={}]
590          *
591          * @fires wp.media.model.Attachments#reset
592          *
593          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
594          */
595         validateAll: function( attachments, options ) {
596                 options = options || {};
597
598                 _.each( attachments.models, function( attachment ) {
599                         this.validate( attachment, { silent: true });
600                 }, this );
601
602                 if ( ! options.silent ) {
603                         this.trigger( 'reset', this, options );
604                 }
605                 return this;
606         },
607         /**
608          * Start observing another attachments collection change events
609          * and replicate them on this collection.
610          *
611          * @param {wp.media.model.Attachments} The attachments collection to observe.
612          * @returns {wp.media.model.Attachments} Returns itself to allow chaining.
613          */
614         observe: function( attachments ) {
615                 this.observers = this.observers || [];
616                 this.observers.push( attachments );
617
618                 attachments.on( 'add change remove', this._validateHandler, this );
619                 attachments.on( 'reset', this._validateAllHandler, this );
620                 this.validateAll( attachments );
621                 return this;
622         },
623         /**
624          * Stop replicating collection change events from another attachments collection.
625          *
626          * @param {wp.media.model.Attachments} The attachments collection to stop observing.
627          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
628          */
629         unobserve: function( attachments ) {
630                 if ( attachments ) {
631                         attachments.off( null, null, this );
632                         this.observers = _.without( this.observers, attachments );
633
634                 } else {
635                         _.each( this.observers, function( attachments ) {
636                                 attachments.off( null, null, this );
637                         }, this );
638                         delete this.observers;
639                 }
640
641                 return this;
642         },
643         /**
644          * @access private
645          *
646          * @param {wp.media.model.Attachments} attachment
647          * @param {wp.media.model.Attachments} attachments
648          * @param {Object} options
649          *
650          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
651          */
652         _validateHandler: function( attachment, attachments, options ) {
653                 // If we're not mirroring this `attachments` collection,
654                 // only retain the `silent` option.
655                 options = attachments === this.mirroring ? options : {
656                         silent: options && options.silent
657                 };
658
659                 return this.validate( attachment, options );
660         },
661         /**
662          * @access private
663          *
664          * @param {wp.media.model.Attachments} attachments
665          * @param {Object} options
666          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
667          */
668         _validateAllHandler: function( attachments, options ) {
669                 return this.validateAll( attachments, options );
670         },
671         /**
672          * Start mirroring another attachments collection, clearing out any models already
673          * in the collection.
674          *
675          * @param {wp.media.model.Attachments} The attachments collection to mirror.
676          * @returns {wp.media.model.Attachments} Returns itself to allow chaining
677          */
678         mirror: function( attachments ) {
679                 if ( this.mirroring && this.mirroring === attachments ) {
680                         return this;
681                 }
682
683                 this.unmirror();
684                 this.mirroring = attachments;
685
686                 // Clear the collection silently. A `reset` event will be fired
687                 // when `observe()` calls `validateAll()`.
688                 this.reset( [], { silent: true } );
689                 this.observe( attachments );
690
691                 return this;
692         },
693         /**
694          * Stop mirroring another attachments collection.
695          */
696         unmirror: function() {
697                 if ( ! this.mirroring ) {
698                         return;
699                 }
700
701                 this.unobserve( this.mirroring );
702                 delete this.mirroring;
703         },
704         /**
705          * Retrive more attachments from the server for the collection.
706          *
707          * Only works if the collection is mirroring a Query Attachments collection,
708          * and forwards to its `more` method. This collection class doesn't have
709          * server persistence by itself.
710          *
711          * @param {object} options
712          * @returns {Promise}
713          */
714         more: function( options ) {
715                 var deferred = jQuery.Deferred(),
716                         mirroring = this.mirroring,
717                         attachments = this;
718
719                 if ( ! mirroring || ! mirroring.more ) {
720                         return deferred.resolveWith( this ).promise();
721                 }
722                 // If we're mirroring another collection, forward `more` to
723                 // the mirrored collection. Account for a race condition by
724                 // checking if we're still mirroring that collection when
725                 // the request resolves.
726                 mirroring.more( options ).done( function() {
727                         if ( this === attachments.mirroring ) {
728                                 deferred.resolveWith( this );
729                         }
730                 });
731
732                 return deferred.promise();
733         },
734         /**
735          * Whether there are more attachments that haven't been sync'd from the server
736          * that match the collection's query.
737          *
738          * Only works if the collection is mirroring a Query Attachments collection,
739          * and forwards to its `hasMore` method. This collection class doesn't have
740          * server persistence by itself.
741          *
742          * @returns {boolean}
743          */
744         hasMore: function() {
745                 return this.mirroring ? this.mirroring.hasMore() : false;
746         },
747         /**
748          * A custom AJAX-response parser.
749          *
750          * See trac ticket #24753
751          *
752          * @param {Object|Array} resp The raw response Object/Array.
753          * @param {Object} xhr
754          * @returns {Array} The array of model attributes to be added to the collection
755          */
756         parse: function( resp, xhr ) {
757                 if ( ! _.isArray( resp ) ) {
758                         resp = [resp];
759                 }
760
761                 return _.map( resp, function( attrs ) {
762                         var id, attachment, newAttributes;
763
764                         if ( attrs instanceof Backbone.Model ) {
765                                 id = attrs.get( 'id' );
766                                 attrs = attrs.attributes;
767                         } else {
768                                 id = attrs.id;
769                         }
770
771                         attachment = wp.media.model.Attachment.get( id );
772                         newAttributes = attachment.parse( attrs, xhr );
773
774                         if ( ! _.isEqual( attachment.attributes, newAttributes ) ) {
775                                 attachment.set( newAttributes );
776                         }
777
778                         return attachment;
779                 });
780         },
781         /**
782          * If the collection is a query, create and mirror an Attachments Query collection.
783          *
784          * @access private
785          */
786         _requery: function( refresh ) {
787                 var props;
788                 if ( this.props.get('query') ) {
789                         props = this.props.toJSON();
790                         props.cache = ( true !== refresh );
791                         this.mirror( wp.media.model.Query.get( props ) );
792                 }
793         },
794         /**
795          * If this collection is sorted by `menuOrder`, recalculates and saves
796          * the menu order to the database.
797          *
798          * @returns {undefined|Promise}
799          */
800         saveMenuOrder: function() {
801                 if ( 'menuOrder' !== this.props.get('orderby') ) {
802                         return;
803                 }
804
805                 // Removes any uploading attachments, updates each attachment's
806                 // menu order, and returns an object with an { id: menuOrder }
807                 // mapping to pass to the request.
808                 var attachments = this.chain().filter( function( attachment ) {
809                         return ! _.isUndefined( attachment.id );
810                 }).map( function( attachment, index ) {
811                         // Indices start at 1.
812                         index = index + 1;
813                         attachment.set( 'menuOrder', index );
814                         return [ attachment.id, index ];
815                 }).object().value();
816
817                 if ( _.isEmpty( attachments ) ) {
818                         return;
819                 }
820
821                 return wp.media.post( 'save-attachment-order', {
822                         nonce:       wp.media.model.settings.post.nonce,
823                         post_id:     wp.media.model.settings.post.id,
824                         attachments: attachments
825                 });
826         }
827 }, {
828         /**
829          * A function to compare two attachment models in an attachments collection.
830          *
831          * Used as the default comparator for instances of wp.media.model.Attachments
832          * and its subclasses. @see wp.media.model.Attachments._changeOrderby().
833          *
834          * @static
835          *
836          * @param {Backbone.Model} a
837          * @param {Backbone.Model} b
838          * @param {Object} options
839          * @returns {Number} -1 if the first model should come before the second,
840          *    0 if they are of the same rank and
841          *    1 if the first model should come after.
842          */
843         comparator: function( a, b, options ) {
844                 var key   = this.props.get('orderby'),
845                         order = this.props.get('order') || 'DESC',
846                         ac    = a.cid,
847                         bc    = b.cid;
848
849                 a = a.get( key );
850                 b = b.get( key );
851
852                 if ( 'date' === key || 'modified' === key ) {
853                         a = a || new Date();
854                         b = b || new Date();
855                 }
856
857                 // If `options.ties` is set, don't enforce the `cid` tiebreaker.
858                 if ( options && options.ties ) {
859                         ac = bc = null;
860                 }
861
862                 return ( 'DESC' === order ) ? wp.media.compare( a, b, ac, bc ) : wp.media.compare( b, a, bc, ac );
863         },
864         /**
865          * @namespace
866          */
867         filters: {
868                 /**
869                  * @static
870                  * Note that this client-side searching is *not* equivalent
871                  * to our server-side searching.
872                  *
873                  * @param {wp.media.model.Attachment} attachment
874                  *
875                  * @this wp.media.model.Attachments
876                  *
877                  * @returns {Boolean}
878                  */
879                 search: function( attachment ) {
880                         if ( ! this.props.get('search') ) {
881                                 return true;
882                         }
883
884                         return _.any(['title','filename','description','caption','name'], function( key ) {
885                                 var value = attachment.get( key );
886                                 return value && -1 !== value.search( this.props.get('search') );
887                         }, this );
888                 },
889                 /**
890                  * @static
891                  * @param {wp.media.model.Attachment} attachment
892                  *
893                  * @this wp.media.model.Attachments
894                  *
895                  * @returns {Boolean}
896                  */
897                 type: function( attachment ) {
898                         var type = this.props.get('type');
899                         return ! type || -1 !== type.indexOf( attachment.get('type') );
900                 },
901                 /**
902                  * @static
903                  * @param {wp.media.model.Attachment} attachment
904                  *
905                  * @this wp.media.model.Attachments
906                  *
907                  * @returns {Boolean}
908                  */
909                 uploadedTo: function( attachment ) {
910                         var uploadedTo = this.props.get('uploadedTo');
911                         if ( _.isUndefined( uploadedTo ) ) {
912                                 return true;
913                         }
914
915                         return uploadedTo === attachment.get('uploadedTo');
916                 },
917                 /**
918                  * @static
919                  * @param {wp.media.model.Attachment} attachment
920                  *
921                  * @this wp.media.model.Attachments
922                  *
923                  * @returns {Boolean}
924                  */
925                 status: function( attachment ) {
926                         var status = this.props.get('status');
927                         if ( _.isUndefined( status ) ) {
928                                 return true;
929                         }
930
931                         return status === attachment.get('status');
932                 }
933         }
934 });
935
936 module.exports = Attachments;
937
938 },{}],4:[function(require,module,exports){
939 /*globals Backbone */
940
941 /**
942  * wp.media.model.PostImage
943  *
944  * An instance of an image that's been embedded into a post.
945  *
946  * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.
947  *
948  * @class
949  * @augments Backbone.Model
950  *
951  * @param {int} [attributes]               Initial model attributes.
952  * @param {int} [attributes.attachment_id] ID of the attachment.
953  **/
954 var PostImage = Backbone.Model.extend({
955
956         initialize: function( attributes ) {
957                 var Attachment = wp.media.model.Attachment;
958                 this.attachment = false;
959
960                 if ( attributes.attachment_id ) {
961                         this.attachment = Attachment.get( attributes.attachment_id );
962                         if ( this.attachment.get( 'url' ) ) {
963                                 this.dfd = jQuery.Deferred();
964                                 this.dfd.resolve();
965                         } else {
966                                 this.dfd = this.attachment.fetch();
967                         }
968                         this.bindAttachmentListeners();
969                 }
970
971                 // keep url in sync with changes to the type of link
972                 this.on( 'change:link', this.updateLinkUrl, this );
973                 this.on( 'change:size', this.updateSize, this );
974
975                 this.setLinkTypeFromUrl();
976                 this.setAspectRatio();
977
978                 this.set( 'originalUrl', attributes.url );
979         },
980
981         bindAttachmentListeners: function() {
982                 this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
983                 this.listenTo( this.attachment, 'sync', this.setAspectRatio );
984                 this.listenTo( this.attachment, 'change', this.updateSize );
985         },
986
987         changeAttachment: function( attachment, props ) {
988                 this.stopListening( this.attachment );
989                 this.attachment = attachment;
990                 this.bindAttachmentListeners();
991
992                 this.set( 'attachment_id', this.attachment.get( 'id' ) );
993                 this.set( 'caption', this.attachment.get( 'caption' ) );
994                 this.set( 'alt', this.attachment.get( 'alt' ) );
995                 this.set( 'size', props.get( 'size' ) );
996                 this.set( 'align', props.get( 'align' ) );
997                 this.set( 'link', props.get( 'link' ) );
998                 this.updateLinkUrl();
999                 this.updateSize();
1000         },
1001
1002         setLinkTypeFromUrl: function() {
1003                 var linkUrl = this.get( 'linkUrl' ),
1004                         type;
1005
1006                 if ( ! linkUrl ) {
1007                         this.set( 'link', 'none' );
1008                         return;
1009                 }
1010
1011                 // default to custom if there is a linkUrl
1012                 type = 'custom';
1013
1014                 if ( this.attachment ) {
1015                         if ( this.attachment.get( 'url' ) === linkUrl ) {
1016                                 type = 'file';
1017                         } else if ( this.attachment.get( 'link' ) === linkUrl ) {
1018                                 type = 'post';
1019                         }
1020                 } else {
1021                         if ( this.get( 'url' ) === linkUrl ) {
1022                                 type = 'file';
1023                         }
1024                 }
1025
1026                 this.set( 'link', type );
1027         },
1028
1029         updateLinkUrl: function() {
1030                 var link = this.get( 'link' ),
1031                         url;
1032
1033                 switch( link ) {
1034                         case 'file':
1035                                 if ( this.attachment ) {
1036                                         url = this.attachment.get( 'url' );
1037                                 } else {
1038                                         url = this.get( 'url' );
1039                                 }
1040                                 this.set( 'linkUrl', url );
1041                                 break;
1042                         case 'post':
1043                                 this.set( 'linkUrl', this.attachment.get( 'link' ) );
1044                                 break;
1045                         case 'none':
1046                                 this.set( 'linkUrl', '' );
1047                                 break;
1048                 }
1049         },
1050
1051         updateSize: function() {
1052                 var size;
1053
1054                 if ( ! this.attachment ) {
1055                         return;
1056                 }
1057
1058                 if ( this.get( 'size' ) === 'custom' ) {
1059                         this.set( 'width', this.get( 'customWidth' ) );
1060                         this.set( 'height', this.get( 'customHeight' ) );
1061                         this.set( 'url', this.get( 'originalUrl' ) );
1062                         return;
1063                 }
1064
1065                 size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
1066
1067                 if ( ! size ) {
1068                         return;
1069                 }
1070
1071                 this.set( 'url', size.url );
1072                 this.set( 'width', size.width );
1073                 this.set( 'height', size.height );
1074         },
1075
1076         setAspectRatio: function() {
1077                 var full;
1078
1079                 if ( this.attachment && this.attachment.get( 'sizes' ) ) {
1080                         full = this.attachment.get( 'sizes' ).full;
1081
1082                         if ( full ) {
1083                                 this.set( 'aspectRatio', full.width / full.height );
1084                                 return;
1085                         }
1086                 }
1087
1088                 this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );
1089         }
1090 });
1091
1092 module.exports = PostImage;
1093
1094 },{}],5:[function(require,module,exports){
1095 /*globals wp, _ */
1096
1097 /**
1098  * wp.media.model.Query
1099  *
1100  * A collection of attachments that match the supplied query arguments.
1101  *
1102  * Note: Do NOT change this.args after the query has been initialized.
1103  *       Things will break.
1104  *
1105  * @class
1106  * @augments wp.media.model.Attachments
1107  * @augments Backbone.Collection
1108  *
1109  * @param {array}  [models]                      Models to initialize with the collection.
1110  * @param {object} [options]                     Options hash.
1111  * @param {object} [options.args]                Attachments query arguments.
1112  * @param {object} [options.args.posts_per_page]
1113  */
1114 var Attachments = wp.media.model.Attachments,
1115         Query;
1116
1117 Query = Attachments.extend({
1118         /**
1119          * @global wp.Uploader
1120          *
1121          * @param {array}  [models=[]]  Array of initial models to populate the collection.
1122          * @param {object} [options={}]
1123          */
1124         initialize: function( models, options ) {
1125                 var allowed;
1126
1127                 options = options || {};
1128                 Attachments.prototype.initialize.apply( this, arguments );
1129
1130                 this.args     = options.args;
1131                 this._hasMore = true;
1132                 this.created  = new Date();
1133
1134                 this.filters.order = function( attachment ) {
1135                         var orderby = this.props.get('orderby'),
1136                                 order = this.props.get('order');
1137
1138                         if ( ! this.comparator ) {
1139                                 return true;
1140                         }
1141
1142                         // We want any items that can be placed before the last
1143                         // item in the set. If we add any items after the last
1144                         // item, then we can't guarantee the set is complete.
1145                         if ( this.length ) {
1146                                 return 1 !== this.comparator( attachment, this.last(), { ties: true });
1147
1148                         // Handle the case where there are no items yet and
1149                         // we're sorting for recent items. In that case, we want
1150                         // changes that occurred after we created the query.
1151                         } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
1152                                 return attachment.get( orderby ) >= this.created;
1153
1154                         // If we're sorting by menu order and we have no items,
1155                         // accept any items that have the default menu order (0).
1156                         } else if ( 'ASC' === order && 'menuOrder' === orderby ) {
1157                                 return attachment.get( orderby ) === 0;
1158                         }
1159
1160                         // Otherwise, we don't want any items yet.
1161                         return false;
1162                 };
1163
1164                 // Observe the central `wp.Uploader.queue` collection to watch for
1165                 // new matches for the query.
1166                 //
1167                 // Only observe when a limited number of query args are set. There
1168                 // are no filters for other properties, so observing will result in
1169                 // false positives in those queries.
1170                 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ];
1171                 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
1172                         this.observe( wp.Uploader.queue );
1173                 }
1174         },
1175         /**
1176          * Whether there are more attachments that haven't been sync'd from the server
1177          * that match the collection's query.
1178          *
1179          * @returns {boolean}
1180          */
1181         hasMore: function() {
1182                 return this._hasMore;
1183         },
1184         /**
1185          * Fetch more attachments from the server for the collection.
1186          *
1187          * @param   {object}  [options={}]
1188          * @returns {Promise}
1189          */
1190         more: function( options ) {
1191                 var query = this;
1192
1193                 // If there is already a request pending, return early with the Deferred object.
1194                 if ( this._more && 'pending' === this._more.state() ) {
1195                         return this._more;
1196                 }
1197
1198                 if ( ! this.hasMore() ) {
1199                         return jQuery.Deferred().resolveWith( this ).promise();
1200                 }
1201
1202                 options = options || {};
1203                 options.remove = false;
1204
1205                 return this._more = this.fetch( options ).done( function( resp ) {
1206                         if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
1207                                 query._hasMore = false;
1208                         }
1209                 });
1210         },
1211         /**
1212          * Overrides Backbone.Collection.sync
1213          * Overrides wp.media.model.Attachments.sync
1214          *
1215          * @param {String} method
1216          * @param {Backbone.Model} model
1217          * @param {Object} [options={}]
1218          * @returns {Promise}
1219          */
1220         sync: function( method, model, options ) {
1221                 var args, fallback;
1222
1223                 // Overload the read method so Attachment.fetch() functions correctly.
1224                 if ( 'read' === method ) {
1225                         options = options || {};
1226                         options.context = this;
1227                         options.data = _.extend( options.data || {}, {
1228                                 action:  'query-attachments',
1229                                 post_id: wp.media.model.settings.post.id
1230                         });
1231
1232                         // Clone the args so manipulation is non-destructive.
1233                         args = _.clone( this.args );
1234
1235                         // Determine which page to query.
1236                         if ( -1 !== args.posts_per_page ) {
1237                                 args.paged = Math.round( this.length / args.posts_per_page ) + 1;
1238                         }
1239
1240                         options.data.query = args;
1241                         return wp.media.ajax( options );
1242
1243                 // Otherwise, fall back to Backbone.sync()
1244                 } else {
1245                         /**
1246                          * Call wp.media.model.Attachments.sync or Backbone.sync
1247                          */
1248                         fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
1249                         return fallback.sync.apply( this, arguments );
1250                 }
1251         }
1252 }, {
1253         /**
1254          * @readonly
1255          */
1256         defaultProps: {
1257                 orderby: 'date',
1258                 order:   'DESC'
1259         },
1260         /**
1261          * @readonly
1262          */
1263         defaultArgs: {
1264                 posts_per_page: 40
1265         },
1266         /**
1267          * @readonly
1268          */
1269         orderby: {
1270                 allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
1271                 /**
1272                  * A map of JavaScript orderby values to their WP_Query equivalents.
1273                  * @type {Object}
1274                  */
1275                 valuemap: {
1276                         'id':         'ID',
1277                         'uploadedTo': 'parent',
1278                         'menuOrder':  'menu_order ID'
1279                 }
1280         },
1281         /**
1282          * A map of JavaScript query properties to their WP_Query equivalents.
1283          *
1284          * @readonly
1285          */
1286         propmap: {
1287                 'search':    's',
1288                 'type':      'post_mime_type',
1289                 'perPage':   'posts_per_page',
1290                 'menuOrder': 'menu_order',
1291                 'uploadedTo': 'post_parent',
1292                 'status':     'post_status',
1293                 'include':    'post__in',
1294                 'exclude':    'post__not_in'
1295         },
1296         /**
1297          * Creates and returns an Attachments Query collection given the properties.
1298          *
1299          * Caches query objects and reuses where possible.
1300          *
1301          * @static
1302          * @method
1303          *
1304          * @param {object} [props]
1305          * @param {Object} [props.cache=true]   Whether to use the query cache or not.
1306          * @param {Object} [props.order]
1307          * @param {Object} [props.orderby]
1308          * @param {Object} [props.include]
1309          * @param {Object} [props.exclude]
1310          * @param {Object} [props.s]
1311          * @param {Object} [props.post_mime_type]
1312          * @param {Object} [props.posts_per_page]
1313          * @param {Object} [props.menu_order]
1314          * @param {Object} [props.post_parent]
1315          * @param {Object} [props.post_status]
1316          * @param {Object} [options]
1317          *
1318          * @returns {wp.media.model.Query} A new Attachments Query collection.
1319          */
1320         get: (function(){
1321                 /**
1322                  * @static
1323                  * @type Array
1324                  */
1325                 var queries = [];
1326
1327                 /**
1328                  * @returns {Query}
1329                  */
1330                 return function( props, options ) {
1331                         var args     = {},
1332                                 orderby  = Query.orderby,
1333                                 defaults = Query.defaultProps,
1334                                 query,
1335                                 cache    = !! props.cache || _.isUndefined( props.cache );
1336
1337                         // Remove the `query` property. This isn't linked to a query,
1338                         // this *is* the query.
1339                         delete props.query;
1340                         delete props.cache;
1341
1342                         // Fill default args.
1343                         _.defaults( props, defaults );
1344
1345                         // Normalize the order.
1346                         props.order = props.order.toUpperCase();
1347                         if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
1348                                 props.order = defaults.order.toUpperCase();
1349                         }
1350
1351                         // Ensure we have a valid orderby value.
1352                         if ( ! _.contains( orderby.allowed, props.orderby ) ) {
1353                                 props.orderby = defaults.orderby;
1354                         }
1355
1356                         _.each( [ 'include', 'exclude' ], function( prop ) {
1357                                 if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) {
1358                                         props[ prop ] = [ props[ prop ] ];
1359                                 }
1360                         } );
1361
1362                         // Generate the query `args` object.
1363                         // Correct any differing property names.
1364                         _.each( props, function( value, prop ) {
1365                                 if ( _.isNull( value ) ) {
1366                                         return;
1367                                 }
1368
1369                                 args[ Query.propmap[ prop ] || prop ] = value;
1370                         });
1371
1372                         // Fill any other default query args.
1373                         _.defaults( args, Query.defaultArgs );
1374
1375                         // `props.orderby` does not always map directly to `args.orderby`.
1376                         // Substitute exceptions specified in orderby.keymap.
1377                         args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
1378
1379                         // Search the query cache for a matching query.
1380                         if ( cache ) {
1381                                 query = _.find( queries, function( query ) {
1382                                         return _.isEqual( query.args, args );
1383                                 });
1384                         } else {
1385                                 queries = [];
1386                         }
1387
1388                         // Otherwise, create a new query and add it to the cache.
1389                         if ( ! query ) {
1390                                 query = new Query( [], _.extend( options || {}, {
1391                                         props: props,
1392                                         args:  args
1393                                 } ) );
1394                                 queries.push( query );
1395                         }
1396
1397                         return query;
1398                 };
1399         }())
1400 });
1401
1402 module.exports = Query;
1403
1404 },{}],6:[function(require,module,exports){
1405 /*globals wp, _ */
1406
1407 /**
1408  * wp.media.model.Selection
1409  *
1410  * A selection of attachments.
1411  *
1412  * @class
1413  * @augments wp.media.model.Attachments
1414  * @augments Backbone.Collection
1415  */
1416 var Attachments = wp.media.model.Attachments,
1417         Selection;
1418
1419 Selection = Attachments.extend({
1420         /**
1421          * Refresh the `single` model whenever the selection changes.
1422          * Binds `single` instead of using the context argument to ensure
1423          * it receives no parameters.
1424          *
1425          * @param {Array} [models=[]] Array of models used to populate the collection.
1426          * @param {Object} [options={}]
1427          */
1428         initialize: function( models, options ) {
1429                 /**
1430                  * call 'initialize' directly on the parent class
1431                  */
1432                 Attachments.prototype.initialize.apply( this, arguments );
1433                 this.multiple = options && options.multiple;
1434
1435                 this.on( 'add remove reset', _.bind( this.single, this, false ) );
1436         },
1437
1438         /**
1439          * If the workflow does not support multi-select, clear out the selection
1440          * before adding a new attachment to it.
1441          *
1442          * @param {Array} models
1443          * @param {Object} options
1444          * @returns {wp.media.model.Attachment[]}
1445          */
1446         add: function( models, options ) {
1447                 if ( ! this.multiple ) {
1448                         this.remove( this.models );
1449                 }
1450                 /**
1451                  * call 'add' directly on the parent class
1452                  */
1453                 return Attachments.prototype.add.call( this, models, options );
1454         },
1455
1456         /**
1457          * Fired when toggling (clicking on) an attachment in the modal.
1458          *
1459          * @param {undefined|boolean|wp.media.model.Attachment} model
1460          *
1461          * @fires wp.media.model.Selection#selection:single
1462          * @fires wp.media.model.Selection#selection:unsingle
1463          *
1464          * @returns {Backbone.Model}
1465          */
1466         single: function( model ) {
1467                 var previous = this._single;
1468
1469                 // If a `model` is provided, use it as the single model.
1470                 if ( model ) {
1471                         this._single = model;
1472                 }
1473                 // If the single model isn't in the selection, remove it.
1474                 if ( this._single && ! this.get( this._single.cid ) ) {
1475                         delete this._single;
1476                 }
1477
1478                 this._single = this._single || this.last();
1479
1480                 // If single has changed, fire an event.
1481                 if ( this._single !== previous ) {
1482                         if ( previous ) {
1483                                 previous.trigger( 'selection:unsingle', previous, this );
1484
1485                                 // If the model was already removed, trigger the collection
1486                                 // event manually.
1487                                 if ( ! this.get( previous.cid ) ) {
1488                                         this.trigger( 'selection:unsingle', previous, this );
1489                                 }
1490                         }
1491                         if ( this._single ) {
1492                                 this._single.trigger( 'selection:single', this._single, this );
1493                         }
1494                 }
1495
1496                 // Return the single model, or the last model as a fallback.
1497                 return this._single;
1498         }
1499 });
1500
1501 module.exports = Selection;
1502
1503 },{}]},{},[1]);