~canonical-sysadmins/wordpress/4.2

« back to all changes in this revision

Viewing changes to wp-includes/js/media-models.js

  • Committer: Paul Gear
  • Date: 2015-04-24 01:35:20 UTC
  • mfrom: (1.1.4 upstream)
  • Revision ID: paul.gear@canonical.com-20150424013520-w4p9ksth76zh6opw
Merge new upstream release 4.2

Show diffs side-by-side

added added

removed removed

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