Diff: STRATO-apps/wordpress_03/app/wp-includes/js/wp-api.js
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
/**
2
+
* @output wp-includes/js/wp-api.js
3
+
*/
4
+
5
+
(function( window, undefined ) {
6
+
7
+
'use strict';
8
+
9
+
/**
10
+
* Initialize the WP_API.
11
+
*/
12
+
function WP_API() {
13
+
/** @namespace wp.api.models */
14
+
this.models = {};
15
+
/** @namespace wp.api.collections */
16
+
this.collections = {};
17
+
/** @namespace wp.api.views */
18
+
this.views = {};
19
+
}
20
+
21
+
/** @namespace wp */
22
+
window.wp = window.wp || {};
23
+
/** @namespace wp.api */
24
+
wp.api = wp.api || new WP_API();
25
+
wp.api.versionString = wp.api.versionString || 'wp/v2/';
26
+
27
+
// Alias _includes to _.contains, ensuring it is available if lodash is used.
28
+
if ( ! _.isFunction( _.includes ) && _.isFunction( _.contains ) ) {
29
+
_.includes = _.contains;
30
+
}
31
+
32
+
})( window );
33
+
34
+
(function( window, undefined ) {
35
+
36
+
'use strict';
37
+
38
+
var pad, r;
39
+
40
+
/** @namespace wp */
41
+
window.wp = window.wp || {};
42
+
/** @namespace wp.api */
43
+
wp.api = wp.api || {};
44
+
/** @namespace wp.api.utils */
45
+
wp.api.utils = wp.api.utils || {};
46
+
47
+
/**
48
+
* Determine model based on API route.
49
+
*
50
+
* @param {string} route The API route.
51
+
*
52
+
* @return {Backbone Model} The model found at given route. Undefined if not found.
53
+
*/
54
+
wp.api.getModelByRoute = function( route ) {
55
+
return _.find( wp.api.models, function( model ) {
56
+
return model.prototype.route && route === model.prototype.route.index;
57
+
} );
58
+
};
59
+
60
+
/**
61
+
* Determine collection based on API route.
62
+
*
63
+
* @param {string} route The API route.
64
+
*
65
+
* @return {Backbone Model} The collection found at given route. Undefined if not found.
66
+
*/
67
+
wp.api.getCollectionByRoute = function( route ) {
68
+
return _.find( wp.api.collections, function( collection ) {
69
+
return collection.prototype.route && route === collection.prototype.route.index;
70
+
} );
71
+
};
72
+
73
+
74
+
/**
75
+
* ECMAScript 5 shim, adapted from MDN.
76
+
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
77
+
*/
78
+
if ( ! Date.prototype.toISOString ) {
79
+
pad = function( number ) {
80
+
r = String( number );
81
+
if ( 1 === r.length ) {
82
+
r = '0' + r;
83
+
}
84
+
85
+
return r;
86
+
};
87
+
88
+
Date.prototype.toISOString = function() {
89
+
return this.getUTCFullYear() +
90
+
'-' + pad( this.getUTCMonth() + 1 ) +
91
+
'-' + pad( this.getUTCDate() ) +
92
+
'T' + pad( this.getUTCHours() ) +
93
+
':' + pad( this.getUTCMinutes() ) +
94
+
':' + pad( this.getUTCSeconds() ) +
95
+
'.' + String( ( this.getUTCMilliseconds() / 1000 ).toFixed( 3 ) ).slice( 2, 5 ) +
96
+
'Z';
97
+
};
98
+
}
99
+
100
+
/**
101
+
* Parse date into ISO8601 format.
102
+
*
103
+
* @param {Date} date.
104
+
*/
105
+
wp.api.utils.parseISO8601 = function( date ) {
106
+
var timestamp, struct, i, k,
107
+
minutesOffset = 0,
108
+
numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
109
+
110
+
/*
111
+
* ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
112
+
* before falling back to any implementation-specific date parsing, so that’s what we do, even if native
113
+
* implementations could be faster.
114
+
*/
115
+
// 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
116
+
if ( ( struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec( date ) ) ) {
117
+
118
+
// Avoid NaN timestamps caused by “undefined” values being passed to Date.UTC.
119
+
for ( i = 0; ( k = numericKeys[i] ); ++i ) {
120
+
struct[k] = +struct[k] || 0;
121
+
}
122
+
123
+
// Allow undefined days and months.
124
+
struct[2] = ( +struct[2] || 1 ) - 1;
125
+
struct[3] = +struct[3] || 1;
126
+
127
+
if ( 'Z' !== struct[8] && undefined !== struct[9] ) {
128
+
minutesOffset = struct[10] * 60 + struct[11];
129
+
130
+
if ( '+' === struct[9] ) {
131
+
minutesOffset = 0 - minutesOffset;
132
+
}
133
+
}
134
+
135
+
timestamp = Date.UTC( struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7] );
136
+
} else {
137
+
timestamp = Date.parse ? Date.parse( date ) : NaN;
138
+
}
139
+
140
+
return timestamp;
141
+
};
142
+
143
+
/**
144
+
* Helper function for getting the root URL.
145
+
* @return {[type]} [description]
146
+
*/
147
+
wp.api.utils.getRootUrl = function() {
148
+
return window.location.origin ?
149
+
window.location.origin + '/' :
150
+
window.location.protocol + '//' + window.location.host + '/';
151
+
};
152
+
153
+
/**
154
+
* Helper for capitalizing strings.
155
+
*/
156
+
wp.api.utils.capitalize = function( str ) {
157
+
if ( _.isUndefined( str ) ) {
158
+
return str;
159
+
}
160
+
return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
161
+
};
162
+
163
+
/**
164
+
* Helper function that capitalizes the first word and camel cases any words starting
165
+
* after dashes, removing the dashes.
166
+
*/
167
+
wp.api.utils.capitalizeAndCamelCaseDashes = function( str ) {
168
+
if ( _.isUndefined( str ) ) {
169
+
return str;
170
+
}
171
+
str = wp.api.utils.capitalize( str );
172
+
173
+
return wp.api.utils.camelCaseDashes( str );
174
+
};
175
+
176
+
/**
177
+
* Helper function to camel case the letter after dashes, removing the dashes.
178
+
*/
179
+
wp.api.utils.camelCaseDashes = function( str ) {
180
+
return str.replace( /-([a-z])/g, function( g ) {
181
+
return g[ 1 ].toUpperCase();
182
+
} );
183
+
};
184
+
185
+
/**
186
+
* Extract a route part based on negative index.
187
+
*
188
+
* @param {string} route The endpoint route.
189
+
* @param {number} part The number of parts from the end of the route to retrieve. Default 1.
190
+
* Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`.
191
+
* @param {string} [versionString] Version string, defaults to `wp.api.versionString`.
192
+
* @param {boolean} [reverse] Whether to reverse the order when extracting the route part. Optional, default false.
193
+
*/
194
+
wp.api.utils.extractRoutePart = function( route, part, versionString, reverse ) {
195
+
var routeParts;
196
+
197
+
part = part || 1;
198
+
versionString = versionString || wp.api.versionString;
199
+
200
+
// Remove versions string from route to avoid returning it.
201
+
if ( 0 === route.indexOf( '/' + versionString ) ) {
202
+
route = route.substr( versionString.length + 1 );
203
+
}
204
+
205
+
routeParts = route.split( '/' );
206
+
if ( reverse ) {
207
+
routeParts = routeParts.reverse();
208
+
}
209
+
if ( _.isUndefined( routeParts[ --part ] ) ) {
210
+
return '';
211
+
}
212
+
return routeParts[ part ];
213
+
};
214
+
215
+
/**
216
+
* Extract a parent name from a passed route.
217
+
*
218
+
* @param {string} route The route to extract a name from.
219
+
*/
220
+
wp.api.utils.extractParentName = function( route ) {
221
+
var name,
222
+
lastSlash = route.lastIndexOf( '_id>[\\d]+)/' );
223
+
224
+
if ( lastSlash < 0 ) {
225
+
return '';
226
+
}
227
+
name = route.substr( 0, lastSlash - 1 );
228
+
name = name.split( '/' );
229
+
name.pop();
230
+
name = name.pop();
231
+
return name;
232
+
};
233
+
234
+
/**
235
+
* Add args and options to a model prototype from a route's endpoints.
236
+
*
237
+
* @param {Array} routeEndpoints Array of route endpoints.
238
+
* @param {Object} modelInstance An instance of the model (or collection)
239
+
* to add the args to.
240
+
*/
241
+
wp.api.utils.decorateFromRoute = function( routeEndpoints, modelInstance ) {
242
+
243
+
/**
244
+
* Build the args based on route endpoint data.
245
+
*/
246
+
_.each( routeEndpoints, function( routeEndpoint ) {
247
+
248
+
// Add post and edit endpoints as model args.
249
+
if ( _.includes( routeEndpoint.methods, 'POST' ) || _.includes( routeEndpoint.methods, 'PUT' ) ) {
250
+
251
+
// Add any non-empty args, merging them into the args object.
252
+
if ( ! _.isEmpty( routeEndpoint.args ) ) {
253
+
254
+
// Set as default if no args yet.
255
+
if ( _.isEmpty( modelInstance.prototype.args ) ) {
256
+
modelInstance.prototype.args = routeEndpoint.args;
257
+
} else {
258
+
259
+
// We already have args, merge these new args in.
260
+
modelInstance.prototype.args = _.extend( modelInstance.prototype.args, routeEndpoint.args );
261
+
}
262
+
}
263
+
} else {
264
+
265
+
// Add GET method as model options.
266
+
if ( _.includes( routeEndpoint.methods, 'GET' ) ) {
267
+
268
+
// Add any non-empty args, merging them into the defaults object.
269
+
if ( ! _.isEmpty( routeEndpoint.args ) ) {
270
+
271
+
// Set as default if no defaults yet.
272
+
if ( _.isEmpty( modelInstance.prototype.options ) ) {
273
+
modelInstance.prototype.options = routeEndpoint.args;
274
+
} else {
275
+
276
+
// We already have options, merge these new args in.
277
+
modelInstance.prototype.options = _.extend( modelInstance.prototype.options, routeEndpoint.args );
278
+
}
279
+
}
280
+
281
+
}
282
+
}
283
+
284
+
} );
285
+
286
+
};
287
+
288
+
/**
289
+
* Add mixins and helpers to models depending on their defaults.
290
+
*
291
+
* @param {Backbone Model} model The model to attach helpers and mixins to.
292
+
* @param {string} modelClassName The classname of the constructed model.
293
+
* @param {Object} loadingObjects An object containing the models and collections we are building.
294
+
*/
295
+
wp.api.utils.addMixinsAndHelpers = function( model, modelClassName, loadingObjects ) {
296
+
297
+
var hasDate = false,
298
+
299
+
/**
300
+
* Array of parseable dates.
301
+
*
302
+
* @type {string[]}.
303
+
*/
304
+
parseableDates = [ 'date', 'modified', 'date_gmt', 'modified_gmt' ],
305
+
306
+
/**
307
+
* Mixin for all content that is time stamped.
308
+
*
309
+
* This mixin converts between mysql timestamps and JavaScript Dates when syncing a model
310
+
* to or from the server. For example, a date stored as `2015-12-27T21:22:24` on the server
311
+
* gets expanded to `Sun Dec 27 2015 14:22:24 GMT-0700 (MST)` when the model is fetched.
312
+
*
313
+
* @type {{toJSON: toJSON, parse: parse}}.
314
+
*/
315
+
TimeStampedMixin = {
316
+
317
+
/**
318
+
* Prepare a JavaScript Date for transmitting to the server.
319
+
*
320
+
* This helper function accepts a field and Date object. It converts the passed Date
321
+
* to an ISO string and sets that on the model field.
322
+
*
323
+
* @param {Date} date A JavaScript date object. WordPress expects dates in UTC.
324
+
* @param {string} field The date field to set. One of 'date', 'date_gmt', 'date_modified'
325
+
* or 'date_modified_gmt'. Optional, defaults to 'date'.
326
+
*/
327
+
setDate: function( date, field ) {
328
+
var theField = field || 'date';
329
+
330
+
// Don't alter non-parsable date fields.
331
+
if ( _.indexOf( parseableDates, theField ) < 0 ) {
332
+
return false;
333
+
}
334
+
335
+
this.set( theField, date.toISOString() );
336
+
},
337
+
338
+
/**
339
+
* Get a JavaScript Date from the passed field.
340
+
*
341
+
* WordPress returns 'date' and 'date_modified' in the timezone of the server as well as
342
+
* UTC dates as 'date_gmt' and 'date_modified_gmt'. Draft posts do not include UTC dates.
343
+
*
344
+
* @param {string} field The date field to set. One of 'date', 'date_gmt', 'date_modified'
345
+
* or 'date_modified_gmt'. Optional, defaults to 'date'.
346
+
*/
347
+
getDate: function( field ) {
348
+
var theField = field || 'date',
349
+
theISODate = this.get( theField );
350
+
351
+
// Only get date fields and non-null values.
352
+
if ( _.indexOf( parseableDates, theField ) < 0 || _.isNull( theISODate ) ) {
353
+
return false;
354
+
}
355
+
356
+
return new Date( wp.api.utils.parseISO8601( theISODate ) );
357
+
}
358
+
},
359
+
360
+
/**
361
+
* Build a helper function to retrieve related model.
362
+
*
363
+
* @param {string} parentModel The parent model.
364
+
* @param {number} modelId The model ID if the object to request
365
+
* @param {string} modelName The model name to use when constructing the model.
366
+
* @param {string} embedSourcePoint Where to check the embedded object for _embed data.
367
+
* @param {string} embedCheckField Which model field to check to see if the model has data.
368
+
*
369
+
* @return {Deferred.promise} A promise which resolves to the constructed model.
370
+
*/
371
+
buildModelGetter = function( parentModel, modelId, modelName, embedSourcePoint, embedCheckField ) {
372
+
var getModel, embeddedObjects, attributes, deferred;
373
+
374
+
deferred = jQuery.Deferred();
375
+
embeddedObjects = parentModel.get( '_embedded' ) || {};
376
+
377
+
// Verify that we have a valid object id.
378
+
if ( ! _.isNumber( modelId ) || 0 === modelId ) {
379
+
deferred.reject();
380
+
return deferred;
381
+
}
382
+
383
+
// If we have embedded object data, use that when constructing the getModel.
384
+
if ( embeddedObjects[ embedSourcePoint ] ) {
385
+
attributes = _.findWhere( embeddedObjects[ embedSourcePoint ], { id: modelId } );
386
+
}
387
+
388
+
// Otherwise use the modelId.
389
+
if ( ! attributes ) {
390
+
attributes = { id: modelId };
391
+
}
392
+
393
+
// Create the new getModel model.
394
+
getModel = new wp.api.models[ modelName ]( attributes );
395
+
396
+
if ( ! getModel.get( embedCheckField ) ) {
397
+
getModel.fetch( {
398
+
success: function( getModel ) {
399
+
deferred.resolve( getModel );
400
+
},
401
+
error: function( getModel, response ) {
402
+
deferred.reject( response );
403
+
}
404
+
} );
405
+
} else {
406
+
// Resolve with the embedded model.
407
+
deferred.resolve( getModel );
408
+
}
409
+
410
+
// Return a promise.
411
+
return deferred.promise();
412
+
},
413
+
414
+
/**
415
+
* Build a helper to retrieve a collection.
416
+
*
417
+
* @param {string} parentModel The parent model.
418
+
* @param {string} collectionName The name to use when constructing the collection.
419
+
* @param {string} embedSourcePoint Where to check the embedded object for _embed data.
420
+
* @param {string} embedIndex An additional optional index for the _embed data.
421
+
*
422
+
* @return {Deferred.promise} A promise which resolves to the constructed collection.
423
+
*/
424
+
buildCollectionGetter = function( parentModel, collectionName, embedSourcePoint, embedIndex ) {
425
+
/**
426
+
* Returns a promise that resolves to the requested collection
427
+
*
428
+
* Uses the embedded data if available, otherwise fetches the
429
+
* data from the server.
430
+
*
431
+
* @return {Deferred.promise} promise Resolves to a wp.api.collections[ collectionName ]
432
+
* collection.
433
+
*/
434
+
var postId, embeddedObjects, getObjects,
435
+
classProperties = '',
436
+
properties = '',
437
+
deferred = jQuery.Deferred();
438
+
439
+
postId = parentModel.get( 'id' );
440
+
embeddedObjects = parentModel.get( '_embedded' ) || {};
441
+
442
+
// Verify that we have a valid post ID.
443
+
if ( ! _.isNumber( postId ) || 0 === postId ) {
444
+
deferred.reject();
445
+
return deferred;
446
+
}
447
+
448
+
// If we have embedded getObjects data, use that when constructing the getObjects.
449
+
if ( ! _.isUndefined( embedSourcePoint ) && ! _.isUndefined( embeddedObjects[ embedSourcePoint ] ) ) {
450
+
451
+
// Some embeds also include an index offset, check for that.
452
+
if ( _.isUndefined( embedIndex ) ) {
453
+
454
+
// Use the embed source point directly.
455
+
properties = embeddedObjects[ embedSourcePoint ];
456
+
} else {
457
+
458
+
// Add the index to the embed source point.
459
+
properties = embeddedObjects[ embedSourcePoint ][ embedIndex ];
460
+
}
461
+
} else {
462
+
463
+
// Otherwise use the postId.
464
+
classProperties = { parent: postId };
465
+
}
466
+
467
+
// Create the new getObjects collection.
468
+
getObjects = new wp.api.collections[ collectionName ]( properties, classProperties );
469
+
470
+
// If we didn’t have embedded getObjects, fetch the getObjects data.
471
+
if ( _.isUndefined( getObjects.models[0] ) ) {
472
+
getObjects.fetch( {
473
+
success: function( getObjects ) {
474
+
475
+
// Add a helper 'parent_post' attribute onto the model.
476
+
setHelperParentPost( getObjects, postId );
477
+
deferred.resolve( getObjects );
478
+
},
479
+
error: function( getModel, response ) {
480
+
deferred.reject( response );
481
+
}
482
+
} );
483
+
} else {
484
+
485
+
// Add a helper 'parent_post' attribute onto the model.
486
+
setHelperParentPost( getObjects, postId );
487
+
deferred.resolve( getObjects );
488
+
}
489
+
490
+
// Return a promise.
491
+
return deferred.promise();
492
+
493
+
},
494
+
495
+
/**
496
+
* Set the model post parent.
497
+
*/
498
+
setHelperParentPost = function( collection, postId ) {
499
+
500
+
// Attach post_parent id to the collection.
501
+
_.each( collection.models, function( model ) {
502
+
model.set( 'parent_post', postId );
503
+
} );
504
+
},
505
+
506
+
/**
507
+
* Add a helper function to handle post Meta.
508
+
*/
509
+
MetaMixin = {
510
+
511
+
/**
512
+
* Get meta by key for a post.
513
+
*
514
+
* @param {string} key The meta key.
515
+
*
516
+
* @return {Object} The post meta value.
517
+
*/
518
+
getMeta: function( key ) {
519
+
var metas = this.get( 'meta' );
520
+
return metas[ key ];
521
+
},
522
+
523
+
/**
524
+
* Get all meta key/values for a post.
525
+
*
526
+
* @return {Object} The post metas, as a key value pair object.
527
+
*/
528
+
getMetas: function() {
529
+
return this.get( 'meta' );
530
+
},
531
+
532
+
/**
533
+
* Set a group of meta key/values for a post.
534
+
*
535
+
* @param {Object} meta The post meta to set, as key/value pairs.
536
+
*/
537
+
setMetas: function( meta ) {
538
+
var metas = this.get( 'meta' );
539
+
_.extend( metas, meta );
540
+
this.set( 'meta', metas );
541
+
},
542
+
543
+
/**
544
+
* Set a single meta value for a post, by key.
545
+
*
546
+
* @param {string} key The meta key.
547
+
* @param {Object} value The meta value.
548
+
*/
549
+
setMeta: function( key, value ) {
550
+
var metas = this.get( 'meta' );
551
+
metas[ key ] = value;
552
+
this.set( 'meta', metas );
553
+
}
554
+
},
555
+
556
+
/**
557
+
* Add a helper function to handle post Revisions.
558
+
*/
559
+
RevisionsMixin = {
560
+
getRevisions: function() {
561
+
return buildCollectionGetter( this, 'PostRevisions' );
562
+
}
563
+
},
564
+
565
+
/**
566
+
* Add a helper function to handle post Tags.
567
+
*/
568
+
TagsMixin = {
569
+
570
+
/**
571
+
* Get the tags for a post.
572
+
*
573
+
* @return {Deferred.promise} promise Resolves to an array of tags.
574
+
*/
575
+
getTags: function() {
576
+
var tagIds = this.get( 'tags' ),
577
+
tags = new wp.api.collections.Tags();
578
+
579
+
// Resolve with an empty array if no tags.
580
+
if ( _.isEmpty( tagIds ) ) {
581
+
return jQuery.Deferred().resolve( [] );
582
+
}
583
+
584
+
return tags.fetch( { data: { include: tagIds } } );
585
+
},
586
+
587
+
/**
588
+
* Set the tags for a post.
589
+
*
590
+
* Accepts an array of tag slugs, or a Tags collection.
591
+
*
592
+
* @param {Array|Backbone.Collection} tags The tags to set on the post.
593
+
*
594
+
*/
595
+
setTags: function( tags ) {
596
+
var allTags, newTag,
597
+
self = this,
598
+
newTags = [];
599
+
600
+
if ( _.isString( tags ) ) {
601
+
return false;
602
+
}
603
+
604
+
// If this is an array of slugs, build a collection.
605
+
if ( _.isArray( tags ) ) {
606
+
607
+
// Get all the tags.
608
+
allTags = new wp.api.collections.Tags();
609
+
allTags.fetch( {
610
+
data: { per_page: 100 },
611
+
success: function( alltags ) {
612
+
613
+
// Find the passed tags and set them up.
614
+
_.each( tags, function( tag ) {
615
+
newTag = new wp.api.models.Tag( alltags.findWhere( { slug: tag } ) );
616
+
617
+
// Tie the new tag to the post.
618
+
newTag.set( 'parent_post', self.get( 'id' ) );
619
+
620
+
// Add the new tag to the collection.
621
+
newTags.push( newTag );
622
+
} );
623
+
tags = new wp.api.collections.Tags( newTags );
624
+
self.setTagsWithCollection( tags );
625
+
}
626
+
} );
627
+
628
+
} else {
629
+
this.setTagsWithCollection( tags );
630
+
}
631
+
},
632
+
633
+
/**
634
+
* Set the tags for a post.
635
+
*
636
+
* Accepts a Tags collection.
637
+
*
638
+
* @param {Array|Backbone.Collection} tags The tags to set on the post.
639
+
*
640
+
*/
641
+
setTagsWithCollection: function( tags ) {
642
+
643
+
// Pluck out the category IDs.
644
+
this.set( 'tags', tags.pluck( 'id' ) );
645
+
return this.save();
646
+
}
647
+
},
648
+
649
+
/**
650
+
* Add a helper function to handle post Categories.
651
+
*/
652
+
CategoriesMixin = {
653
+
654
+
/**
655
+
* Get a the categories for a post.
656
+
*
657
+
* @return {Deferred.promise} promise Resolves to an array of categories.
658
+
*/
659
+
getCategories: function() {
660
+
var categoryIds = this.get( 'categories' ),
661
+
categories = new wp.api.collections.Categories();
662
+
663
+
// Resolve with an empty array if no categories.
664
+
if ( _.isEmpty( categoryIds ) ) {
665
+
return jQuery.Deferred().resolve( [] );
666
+
}
667
+
668
+
return categories.fetch( { data: { include: categoryIds } } );
669
+
},
670
+
671
+
/**
672
+
* Set the categories for a post.
673
+
*
674
+
* Accepts an array of category slugs, or a Categories collection.
675
+
*
676
+
* @param {Array|Backbone.Collection} categories The categories to set on the post.
677
+
*
678
+
*/
679
+
setCategories: function( categories ) {
680
+
var allCategories, newCategory,
681
+
self = this,
682
+
newCategories = [];
683
+
684
+
if ( _.isString( categories ) ) {
685
+
return false;
686
+
}
687
+
688
+
// If this is an array of slugs, build a collection.
689
+
if ( _.isArray( categories ) ) {
690
+
691
+
// Get all the categories.
692
+
allCategories = new wp.api.collections.Categories();
693
+
allCategories.fetch( {
694
+
data: { per_page: 100 },
695
+
success: function( allcats ) {
696
+
697
+
// Find the passed categories and set them up.
698
+
_.each( categories, function( category ) {
699
+
newCategory = new wp.api.models.Category( allcats.findWhere( { slug: category } ) );
700
+
701
+
// Tie the new category to the post.
702
+
newCategory.set( 'parent_post', self.get( 'id' ) );
703
+
704
+
// Add the new category to the collection.
705
+
newCategories.push( newCategory );
706
+
} );
707
+
categories = new wp.api.collections.Categories( newCategories );
708
+
self.setCategoriesWithCollection( categories );
709
+
}
710
+
} );
711
+
712
+
} else {
713
+
this.setCategoriesWithCollection( categories );
714
+
}
715
+
716
+
},
717
+
718
+
/**
719
+
* Set the categories for a post.
720
+
*
721
+
* Accepts Categories collection.
722
+
*
723
+
* @param {Array|Backbone.Collection} categories The categories to set on the post.
724
+
*
725
+
*/
726
+
setCategoriesWithCollection: function( categories ) {
727
+
728
+
// Pluck out the category IDs.
729
+
this.set( 'categories', categories.pluck( 'id' ) );
730
+
return this.save();
731
+
}
732
+
},
733
+
734
+
/**
735
+
* Add a helper function to retrieve the author user model.
736
+
*/
737
+
AuthorMixin = {
738
+
getAuthorUser: function() {
739
+
return buildModelGetter( this, this.get( 'author' ), 'User', 'author', 'name' );
740
+
}
741
+
},
742
+
743
+
/**
744
+
* Add a helper function to retrieve the featured media.
745
+
*/
746
+
FeaturedMediaMixin = {
747
+
getFeaturedMedia: function() {
748
+
return buildModelGetter( this, this.get( 'featured_media' ), 'Media', 'wp:featuredmedia', 'source_url' );
749
+
}
750
+
};
751
+
752
+
// Exit if we don't have valid model defaults.
753
+
if ( _.isUndefined( model.prototype.args ) ) {
754
+
return model;
755
+
}
756
+
757
+
// Go thru the parsable date fields, if our model contains any of them it gets the TimeStampedMixin.
758
+
_.each( parseableDates, function( theDateKey ) {
759
+
if ( ! _.isUndefined( model.prototype.args[ theDateKey ] ) ) {
760
+
hasDate = true;
761
+
}
762
+
} );
763
+
764
+
// Add the TimeStampedMixin for models that contain a date field.
765
+
if ( hasDate ) {
766
+
model = model.extend( TimeStampedMixin );
767
+
}
768
+
769
+
// Add the AuthorMixin for models that contain an author.
770
+
if ( ! _.isUndefined( model.prototype.args.author ) ) {
771
+
model = model.extend( AuthorMixin );
772
+
}
773
+
774
+
// Add the FeaturedMediaMixin for models that contain a featured_media.
775
+
if ( ! _.isUndefined( model.prototype.args.featured_media ) ) {
776
+
model = model.extend( FeaturedMediaMixin );
777
+
}
778
+
779
+
// Add the CategoriesMixin for models that support categories collections.
780
+
if ( ! _.isUndefined( model.prototype.args.categories ) ) {
781
+
model = model.extend( CategoriesMixin );
782
+
}
783
+
784
+
// Add the MetaMixin for models that support meta.
785
+
if ( ! _.isUndefined( model.prototype.args.meta ) ) {
786
+
model = model.extend( MetaMixin );
787
+
}
788
+
789
+
// Add the TagsMixin for models that support tags collections.
790
+
if ( ! _.isUndefined( model.prototype.args.tags ) ) {
791
+
model = model.extend( TagsMixin );
792
+
}
793
+
794
+
// Add the RevisionsMixin for models that support revisions collections.
795
+
if ( ! _.isUndefined( loadingObjects.collections[ modelClassName + 'Revisions' ] ) ) {
796
+
model = model.extend( RevisionsMixin );
797
+
}
798
+
799
+
return model;
800
+
};
801
+
802
+
})( window );
803
+
804
+
/* global wpApiSettings:false */
805
+
806
+
// Suppress warning about parse function's unused "options" argument:
807
+
/* jshint unused:false */
808
+
(function() {
809
+
810
+
'use strict';
811
+
812
+
var wpApiSettings = window.wpApiSettings || {},
813
+
trashableTypes = [ 'Comment', 'Media', 'Comment', 'Post', 'Page', 'Status', 'Taxonomy', 'Type' ];
814
+
815
+
/**
816
+
* Backbone base model for all models.
817
+
*/
818
+
wp.api.WPApiBaseModel = Backbone.Model.extend(
819
+
/** @lends WPApiBaseModel.prototype */
820
+
{
821
+
822
+
// Initialize the model.
823
+
initialize: function() {
824
+
825
+
/**
826
+
* Types that don't support trashing require passing ?force=true to delete.
827
+
*
828
+
*/
829
+
if ( -1 === _.indexOf( trashableTypes, this.name ) ) {
830
+
this.requireForceForDelete = true;
831
+
}
832
+
},
833
+
834
+
/**
835
+
* Set nonce header before every Backbone sync.
836
+
*
837
+
* @param {string} method.
838
+
* @param {Backbone.Model} model.
839
+
* @param {{beforeSend}, *} options.
840
+
* @return {*}.
841
+
*/
842
+
sync: function( method, model, options ) {
843
+
var beforeSend;
844
+
845
+
options = options || {};
846
+
847
+
// Remove date_gmt if null.
848
+
if ( _.isNull( model.get( 'date_gmt' ) ) ) {
849
+
model.unset( 'date_gmt' );
850
+
}
851
+
852
+
// Remove slug if empty.
853
+
if ( _.isEmpty( model.get( 'slug' ) ) ) {
854
+
model.unset( 'slug' );
855
+
}
856
+
857
+
if ( _.isFunction( model.nonce ) && ! _.isEmpty( model.nonce() ) ) {
858
+
beforeSend = options.beforeSend;
859
+
860
+
// @todo Enable option for jsonp endpoints.
861
+
// options.dataType = 'jsonp';
862
+
863
+
// Include the nonce with requests.
864
+
options.beforeSend = function( xhr ) {
865
+
xhr.setRequestHeader( 'X-WP-Nonce', model.nonce() );
866
+
867
+
if ( beforeSend ) {
868
+
return beforeSend.apply( this, arguments );
869
+
}
870
+
};
871
+
872
+
// Update the nonce when a new nonce is returned with the response.
873
+
options.complete = function( xhr ) {
874
+
var returnedNonce = xhr.getResponseHeader( 'X-WP-Nonce' );
875
+
876
+
if ( returnedNonce && _.isFunction( model.nonce ) && model.nonce() !== returnedNonce ) {
877
+
model.endpointModel.set( 'nonce', returnedNonce );
878
+
}
879
+
};
880
+
}
881
+
882
+
// Add '?force=true' to use delete method when required.
883
+
if ( this.requireForceForDelete && 'delete' === method ) {
884
+
model.url = model.url() + '?force=true';
885
+
}
886
+
return Backbone.sync( method, model, options );
887
+
},
888
+
889
+
/**
890
+
* Save is only allowed when the PUT OR POST methods are available for the endpoint.
891
+
*/
892
+
save: function( attrs, options ) {
893
+
894
+
// Do we have the put method, then execute the save.
895
+
if ( _.includes( this.methods, 'PUT' ) || _.includes( this.methods, 'POST' ) ) {
896
+
897
+
// Proxy the call to the original save function.
898
+
return Backbone.Model.prototype.save.call( this, attrs, options );
899
+
} else {
900
+
901
+
// Otherwise bail, disallowing action.
902
+
return false;
903
+
}
904
+
},
905
+
906
+
/**
907
+
* Delete is only allowed when the DELETE method is available for the endpoint.
908
+
*/
909
+
destroy: function( options ) {
910
+
911
+
// Do we have the DELETE method, then execute the destroy.
912
+
if ( _.includes( this.methods, 'DELETE' ) ) {
913
+
914
+
// Proxy the call to the original save function.
915
+
return Backbone.Model.prototype.destroy.call( this, options );
916
+
} else {
917
+
918
+
// Otherwise bail, disallowing action.
919
+
return false;
920
+
}
921
+
}
922
+
923
+
}
924
+
);
925
+
926
+
/**
927
+
* API Schema model. Contains meta information about the API.
928
+
*/
929
+
wp.api.models.Schema = wp.api.WPApiBaseModel.extend(
930
+
/** @lends Schema.prototype */
931
+
{
932
+
defaults: {
933
+
_links: {},
934
+
namespace: null,
935
+
routes: {}
936
+
},
937
+
938
+
initialize: function( attributes, options ) {
939
+
var model = this;
940
+
options = options || {};
941
+
942
+
wp.api.WPApiBaseModel.prototype.initialize.call( model, attributes, options );
943
+
944
+
model.apiRoot = options.apiRoot || wpApiSettings.root;
945
+
model.versionString = options.versionString || wpApiSettings.versionString;
946
+
},
947
+
948
+
url: function() {
949
+
return this.apiRoot + this.versionString;
950
+
}
951
+
}
952
+
);
953
+
})();
954
+
955
+
( function() {
956
+
957
+
'use strict';
958
+
959
+
var wpApiSettings = window.wpApiSettings || {};
960
+
961
+
/**
962
+
* Contains basic collection functionality such as pagination.
963
+
*/
964
+
wp.api.WPApiBaseCollection = Backbone.Collection.extend(
965
+
/** @lends BaseCollection.prototype */
966
+
{
967
+
968
+
/**
969
+
* Setup default state.
970
+
*/
971
+
initialize: function( models, options ) {
972
+
this.state = {
973
+
data: {},
974
+
currentPage: null,
975
+
totalPages: null,
976
+
totalObjects: null
977
+
};
978
+
if ( _.isUndefined( options ) ) {
979
+
this.parent = '';
980
+
} else {
981
+
this.parent = options.parent;
982
+
}
983
+
},
984
+
985
+
/**
986
+
* Extend Backbone.Collection.sync to add nince and pagination support.
987
+
*
988
+
* Set nonce header before every Backbone sync.
989
+
*
990
+
* @param {string} method.
991
+
* @param {Backbone.Model} model.
992
+
* @param {{success}, *} options.
993
+
* @return {*}.
994
+
*/
995
+
sync: function( method, model, options ) {
996
+
var beforeSend, success,
997
+
self = this;
998
+
999
+
options = options || {};
1000
+
1001
+
if ( _.isFunction( model.nonce ) && ! _.isEmpty( model.nonce() ) ) {
1002
+
beforeSend = options.beforeSend;
1003
+
1004
+
// Include the nonce with requests.
1005
+
options.beforeSend = function( xhr ) {
1006
+
xhr.setRequestHeader( 'X-WP-Nonce', model.nonce() );
1007
+
1008
+
if ( beforeSend ) {
1009
+
return beforeSend.apply( self, arguments );
1010
+
}
1011
+
};
1012
+
1013
+
// Update the nonce when a new nonce is returned with the response.
1014
+
options.complete = function( xhr ) {
1015
+
var returnedNonce = xhr.getResponseHeader( 'X-WP-Nonce' );
1016
+
1017
+
if ( returnedNonce && _.isFunction( model.nonce ) && model.nonce() !== returnedNonce ) {
1018
+
model.endpointModel.set( 'nonce', returnedNonce );
1019
+
}
1020
+
};
1021
+
}
1022
+
1023
+
// When reading, add pagination data.
1024
+
if ( 'read' === method ) {
1025
+
if ( options.data ) {
1026
+
self.state.data = _.clone( options.data );
1027
+
1028
+
delete self.state.data.page;
1029
+
} else {
1030
+
self.state.data = options.data = {};
1031
+
}
1032
+
1033
+
if ( 'undefined' === typeof options.data.page ) {
1034
+
self.state.currentPage = null;
1035
+
self.state.totalPages = null;
1036
+
self.state.totalObjects = null;
1037
+
} else {
1038
+
self.state.currentPage = options.data.page - 1;
1039
+
}
1040
+
1041
+
success = options.success;
1042
+
options.success = function( data, textStatus, request ) {
1043
+
if ( ! _.isUndefined( request ) ) {
1044
+
self.state.totalPages = parseInt( request.getResponseHeader( 'x-wp-totalpages' ), 10 );
1045
+
self.state.totalObjects = parseInt( request.getResponseHeader( 'x-wp-total' ), 10 );
1046
+
}
1047
+
1048
+
if ( null === self.state.currentPage ) {
1049
+
self.state.currentPage = 1;
1050
+
} else {
1051
+
self.state.currentPage++;
1052
+
}
1053
+
1054
+
if ( success ) {
1055
+
return success.apply( this, arguments );
1056
+
}
1057
+
};
1058
+
}
1059
+
1060
+
// Continue by calling Backbone's sync.
1061
+
return Backbone.sync( method, model, options );
1062
+
},
1063
+
1064
+
/**
1065
+
* Fetches the next page of objects if a new page exists.
1066
+
*
1067
+
* @param {data: {page}} options.
1068
+
* @return {*}.
1069
+
*/
1070
+
more: function( options ) {
1071
+
options = options || {};
1072
+
options.data = options.data || {};
1073
+
1074
+
_.extend( options.data, this.state.data );
1075
+
1076
+
if ( 'undefined' === typeof options.data.page ) {
1077
+
if ( ! this.hasMore() ) {
1078
+
return false;
1079
+
}
1080
+
1081
+
if ( null === this.state.currentPage || this.state.currentPage <= 1 ) {
1082
+
options.data.page = 2;
1083
+
} else {
1084
+
options.data.page = this.state.currentPage + 1;
1085
+
}
1086
+
}
1087
+
1088
+
return this.fetch( options );
1089
+
},
1090
+
1091
+
/**
1092
+
* Returns true if there are more pages of objects available.
1093
+
*
1094
+
* @return {null|boolean}
1095
+
*/
1096
+
hasMore: function() {
1097
+
if ( null === this.state.totalPages ||
1098
+
null === this.state.totalObjects ||
1099
+
null === this.state.currentPage ) {
1100
+
return null;
1101
+
} else {
1102
+
return ( this.state.currentPage < this.state.totalPages );
1103
+
}
1104
+
}
1105
+
}
1106
+
);
1107
+
1108
+
} )();
1109
+
1110
+
( function() {
1111
+
1112
+
'use strict';
1113
+
1114
+
var Endpoint, initializedDeferreds = {},
1115
+
wpApiSettings = window.wpApiSettings || {};
1116
+
1117
+
/** @namespace wp */
1118
+
window.wp = window.wp || {};
1119
+
1120
+
/** @namespace wp.api */
1121
+
wp.api = wp.api || {};
1122
+
1123
+
// If wpApiSettings is unavailable, try the default.
1124
+
if ( _.isEmpty( wpApiSettings ) ) {
1125
+
wpApiSettings.root = window.location.origin + '/wp-json/';
1126
+
}
1127
+
1128
+
Endpoint = Backbone.Model.extend(/** @lends Endpoint.prototype */{
1129
+
defaults: {
1130
+
apiRoot: wpApiSettings.root,
1131
+
versionString: wp.api.versionString,
1132
+
nonce: null,
1133
+
schema: null,
1134
+
models: {},
1135
+
collections: {}
1136
+
},
1137
+
1138
+
/**
1139
+
* Initialize the Endpoint model.
1140
+
*/
1141
+
initialize: function() {
1142
+
var model = this, deferred;
1143
+
1144
+
Backbone.Model.prototype.initialize.apply( model, arguments );
1145
+
1146
+
deferred = jQuery.Deferred();
1147
+
model.schemaConstructed = deferred.promise();
1148
+
1149
+
model.schemaModel = new wp.api.models.Schema( null, {
1150
+
apiRoot: model.get( 'apiRoot' ),
1151
+
versionString: model.get( 'versionString' ),
1152
+
nonce: model.get( 'nonce' )
1153
+
} );
1154
+
1155
+
// When the model loads, resolve the promise.
1156
+
model.schemaModel.once( 'change', function() {
1157
+
model.constructFromSchema();
1158
+
deferred.resolve( model );
1159
+
} );
1160
+
1161
+
if ( model.get( 'schema' ) ) {
1162
+
1163
+
// Use schema supplied as model attribute.
1164
+
model.schemaModel.set( model.schemaModel.parse( model.get( 'schema' ) ) );
1165
+
} else if (
1166
+
! _.isUndefined( sessionStorage ) &&
1167
+
( _.isUndefined( wpApiSettings.cacheSchema ) || wpApiSettings.cacheSchema ) &&
1168
+
sessionStorage.getItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ) )
1169
+
) {
1170
+
1171
+
// Used a cached copy of the schema model if available.
1172
+
model.schemaModel.set( model.schemaModel.parse( JSON.parse( sessionStorage.getItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ) ) ) ) );
1173
+
} else {
1174
+
model.schemaModel.fetch( {
1175
+
/**
1176
+
* When the server returns the schema model data, store the data in a sessionCache so we don't
1177
+
* have to retrieve it again for this session. Then, construct the models and collections based
1178
+
* on the schema model data.
1179
+
*
1180
+
* @ignore
1181
+
*/
1182
+
success: function( newSchemaModel ) {
1183
+
1184
+
// Store a copy of the schema model in the session cache if available.
1185
+
if ( ! _.isUndefined( sessionStorage ) && ( _.isUndefined( wpApiSettings.cacheSchema ) || wpApiSettings.cacheSchema ) ) {
1186
+
try {
1187
+
sessionStorage.setItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ), JSON.stringify( newSchemaModel ) );
1188
+
} catch ( error ) {
1189
+
1190
+
// Fail silently, fixes errors in safari private mode.
1191
+
}
1192
+
}
1193
+
},
1194
+
1195
+
// Log the error condition.
1196
+
error: function( err ) {
1197
+
window.console.log( err );
1198
+
}
1199
+
} );
1200
+
}
1201
+
},
1202
+
1203
+
constructFromSchema: function() {
1204
+
var routeModel = this, modelRoutes, collectionRoutes, schemaRoot, loadingObjects,
1205
+
1206
+
/**
1207
+
* Set up the model and collection name mapping options. As the schema is built, the
1208
+
* model and collection names will be adjusted if they are found in the mapping object.
1209
+
*
1210
+
* Localizing a variable wpApiSettings.mapping will over-ride the default mapping options.
1211
+
*
1212
+
*/
1213
+
mapping = wpApiSettings.mapping || {
1214
+
models: {
1215
+
'Categories': 'Category',
1216
+
'Comments': 'Comment',
1217
+
'Pages': 'Page',
1218
+
'PagesMeta': 'PageMeta',
1219
+
'PagesRevisions': 'PageRevision',
1220
+
'Posts': 'Post',
1221
+
'PostsCategories': 'PostCategory',
1222
+
'PostsRevisions': 'PostRevision',
1223
+
'PostsTags': 'PostTag',
1224
+
'Schema': 'Schema',
1225
+
'Statuses': 'Status',
1226
+
'Tags': 'Tag',
1227
+
'Taxonomies': 'Taxonomy',
1228
+
'Types': 'Type',
1229
+
'Users': 'User'
1230
+
},
1231
+
collections: {
1232
+
'PagesMeta': 'PageMeta',
1233
+
'PagesRevisions': 'PageRevisions',
1234
+
'PostsCategories': 'PostCategories',
1235
+
'PostsMeta': 'PostMeta',
1236
+
'PostsRevisions': 'PostRevisions',
1237
+
'PostsTags': 'PostTags'
1238
+
}
1239
+
},
1240
+
1241
+
modelEndpoints = routeModel.get( 'modelEndpoints' ),
1242
+
modelRegex = new RegExp( '(?:.*[+)]|\/(' + modelEndpoints.join( '|' ) + '))$' );
1243
+
1244
+
/**
1245
+
* Iterate thru the routes, picking up models and collections to build. Builds two arrays,
1246
+
* one for models and one for collections.
1247
+
*/
1248
+
modelRoutes = [];
1249
+
collectionRoutes = [];
1250
+
schemaRoot = routeModel.get( 'apiRoot' ).replace( wp.api.utils.getRootUrl(), '' );
1251
+
loadingObjects = {};
1252
+
1253
+
/**
1254
+
* Tracking objects for models and collections.
1255
+
*/
1256
+
loadingObjects.models = {};
1257
+
loadingObjects.collections = {};
1258
+
1259
+
_.each( routeModel.schemaModel.get( 'routes' ), function( route, index ) {
1260
+
1261
+
// Skip the schema root if included in the schema.
1262
+
if ( index !== routeModel.get( ' versionString' ) &&
1263
+
index !== schemaRoot &&
1264
+
index !== ( '/' + routeModel.get( 'versionString' ).slice( 0, -1 ) )
1265
+
) {
1266
+
1267
+
// Single items end with a regex, or a special case word.
1268
+
if ( modelRegex.test( index ) ) {
1269
+
modelRoutes.push( { index: index, route: route } );
1270
+
} else {
1271
+
1272
+
// Collections end in a name.
1273
+
collectionRoutes.push( { index: index, route: route } );
1274
+
}
1275
+
}
1276
+
} );
1277
+
1278
+
/**
1279
+
* Construct the models.
1280
+
*
1281
+
* Base the class name on the route endpoint.
1282
+
*/
1283
+
_.each( modelRoutes, function( modelRoute ) {
1284
+
1285
+
// Extract the name and any parent from the route.
1286
+
var modelClassName,
1287
+
routeName = wp.api.utils.extractRoutePart( modelRoute.index, 2, routeModel.get( 'versionString' ), true ),
1288
+
parentName = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), false ),
1289
+
routeEnd = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), true );
1290
+
1291
+
// Clear the parent part of the rouite if its actually the version string.
1292
+
if ( parentName === routeModel.get( 'versionString' ) ) {
1293
+
parentName = '';
1294
+
}
1295
+
1296
+
// Handle the special case of the 'me' route.
1297
+
if ( 'me' === routeEnd ) {
1298
+
routeName = 'me';
1299
+
}
1300
+
1301
+
// If the model has a parent in its route, add that to its class name.
1302
+
if ( '' !== parentName && parentName !== routeName ) {
1303
+
modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
1304
+
modelClassName = mapping.models[ modelClassName ] || modelClassName;
1305
+
loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {
1306
+
1307
+
// Return a constructed url based on the parent and id.
1308
+
url: function() {
1309
+
var url =
1310
+
routeModel.get( 'apiRoot' ) +
1311
+
routeModel.get( 'versionString' ) +
1312
+
parentName + '/' +
1313
+
( ( _.isUndefined( this.get( 'parent' ) ) || 0 === this.get( 'parent' ) ) ?
1314
+
( _.isUndefined( this.get( 'parent_post' ) ) ? '' : this.get( 'parent_post' ) + '/' ) :
1315
+
this.get( 'parent' ) + '/' ) +
1316
+
routeName;
1317
+
1318
+
if ( ! _.isUndefined( this.get( 'id' ) ) ) {
1319
+
url += '/' + this.get( 'id' );
1320
+
}
1321
+
return url;
1322
+
},
1323
+
1324
+
// Track nonces on the Endpoint 'routeModel'.
1325
+
nonce: function() {
1326
+
return routeModel.get( 'nonce' );
1327
+
},
1328
+
1329
+
endpointModel: routeModel,
1330
+
1331
+
// Include a reference to the original route object.
1332
+
route: modelRoute,
1333
+
1334
+
// Include a reference to the original class name.
1335
+
name: modelClassName,
1336
+
1337
+
// Include the array of route methods for easy reference.
1338
+
methods: modelRoute.route.methods,
1339
+
1340
+
// Include the array of route endpoints for easy reference.
1341
+
endpoints: modelRoute.route.endpoints
1342
+
} );
1343
+
} else {
1344
+
1345
+
// This is a model without a parent in its route.
1346
+
modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
1347
+
modelClassName = mapping.models[ modelClassName ] || modelClassName;
1348
+
loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {
1349
+
1350
+
// Function that returns a constructed url based on the ID.
1351
+
url: function() {
1352
+
var url = routeModel.get( 'apiRoot' ) +
1353
+
routeModel.get( 'versionString' ) +
1354
+
( ( 'me' === routeName ) ? 'users/me' : routeName );
1355
+
1356
+
if ( ! _.isUndefined( this.get( 'id' ) ) ) {
1357
+
url += '/' + this.get( 'id' );
1358
+
}
1359
+
return url;
1360
+
},
1361
+
1362
+
// Track nonces at the Endpoint level.
1363
+
nonce: function() {
1364
+
return routeModel.get( 'nonce' );
1365
+
},
1366
+
1367
+
endpointModel: routeModel,
1368
+
1369
+
// Include a reference to the original route object.
1370
+
route: modelRoute,
1371
+
1372
+
// Include a reference to the original class name.
1373
+
name: modelClassName,
1374
+
1375
+
// Include the array of route methods for easy reference.
1376
+
methods: modelRoute.route.methods,
1377
+
1378
+
// Include the array of route endpoints for easy reference.
1379
+
endpoints: modelRoute.route.endpoints
1380
+
} );
1381
+
}
1382
+
1383
+
// Add defaults to the new model, pulled form the endpoint.
1384
+
wp.api.utils.decorateFromRoute(
1385
+
modelRoute.route.endpoints,
1386
+
loadingObjects.models[ modelClassName ],
1387
+
routeModel.get( 'versionString' )
1388
+
);
1389
+
1390
+
} );
1391
+
1392
+
/**
1393
+
* Construct the collections.
1394
+
*
1395
+
* Base the class name on the route endpoint.
1396
+
*/
1397
+
_.each( collectionRoutes, function( collectionRoute ) {
1398
+
1399
+
// Extract the name and any parent from the route.
1400
+
var collectionClassName, modelClassName,
1401
+
routeName = collectionRoute.index.slice( collectionRoute.index.lastIndexOf( '/' ) + 1 ),
1402
+
parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 1, routeModel.get( 'versionString' ), false );
1403
+
1404
+
// If the collection has a parent in its route, add that to its class name.
1405
+
if ( '' !== parentName && parentName !== routeName && routeModel.get( 'versionString' ) !== parentName ) {
1406
+
1407
+
collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
1408
+
modelClassName = mapping.models[ collectionClassName ] || collectionClassName;
1409
+
collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName;
1410
+
loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( {
1411
+
1412
+
// Function that returns a constructed url passed on the parent.
1413
+
url: function() {
1414
+
return routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) +
1415
+
parentName + '/' +
1416
+
( ( _.isUndefined( this.parent ) || '' === this.parent ) ?
1417
+
( _.isUndefined( this.get( 'parent_post' ) ) ? '' : this.get( 'parent_post' ) + '/' ) :
1418
+
this.parent + '/' ) +
1419
+
routeName;
1420
+
},
1421
+
1422
+
// Specify the model that this collection contains.
1423
+
model: function( attrs, options ) {
1424
+
return new loadingObjects.models[ modelClassName ]( attrs, options );
1425
+
},
1426
+
1427
+
// Track nonces at the Endpoint level.
1428
+
nonce: function() {
1429
+
return routeModel.get( 'nonce' );
1430
+
},
1431
+
1432
+
endpointModel: routeModel,
1433
+
1434
+
// Include a reference to the original class name.
1435
+
name: collectionClassName,
1436
+
1437
+
// Include a reference to the original route object.
1438
+
route: collectionRoute,
1439
+
1440
+
// Include the array of route methods for easy reference.
1441
+
methods: collectionRoute.route.methods
1442
+
} );
1443
+
} else {
1444
+
1445
+
// This is a collection without a parent in its route.
1446
+
collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
1447
+
modelClassName = mapping.models[ collectionClassName ] || collectionClassName;
1448
+
collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName;
1449
+
loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( {
1450
+
1451
+
// For the url of a root level collection, use a string.
1452
+
url: function() {
1453
+
return routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + routeName;
1454
+
},
1455
+
1456
+
// Specify the model that this collection contains.
1457
+
model: function( attrs, options ) {
1458
+
return new loadingObjects.models[ modelClassName ]( attrs, options );
1459
+
},
1460
+
1461
+
// Track nonces at the Endpoint level.
1462
+
nonce: function() {
1463
+
return routeModel.get( 'nonce' );
1464
+
},
1465
+
1466
+
endpointModel: routeModel,
1467
+
1468
+
// Include a reference to the original class name.
1469
+
name: collectionClassName,
1470
+
1471
+
// Include a reference to the original route object.
1472
+
route: collectionRoute,
1473
+
1474
+
// Include the array of route methods for easy reference.
1475
+
methods: collectionRoute.route.methods
1476
+
} );
1477
+
}
1478
+
1479
+
// Add defaults to the new model, pulled form the endpoint.
1480
+
wp.api.utils.decorateFromRoute( collectionRoute.route.endpoints, loadingObjects.collections[ collectionClassName ] );
1481
+
} );
1482
+
1483
+
// Add mixins and helpers for each of the models.
1484
+
_.each( loadingObjects.models, function( model, index ) {
1485
+
loadingObjects.models[ index ] = wp.api.utils.addMixinsAndHelpers( model, index, loadingObjects );
1486
+
} );
1487
+
1488
+
// Set the routeModel models and collections.
1489
+
routeModel.set( 'models', loadingObjects.models );
1490
+
routeModel.set( 'collections', loadingObjects.collections );
1491
+
1492
+
}
1493
+
1494
+
} );
1495
+
1496
+
wp.api.endpoints = new Backbone.Collection();
1497
+
1498
+
/**
1499
+
* Initialize the wp-api, optionally passing the API root.
1500
+
*
1501
+
* @param {Object} [args]
1502
+
* @param {string} [args.nonce] The nonce. Optional, defaults to wpApiSettings.nonce.
1503
+
* @param {string} [args.apiRoot] The api root. Optional, defaults to wpApiSettings.root.
1504
+
* @param {string} [args.versionString] The version string. Optional, defaults to wpApiSettings.root.
1505
+
* @param {Object} [args.schema] The schema. Optional, will be fetched from API if not provided.
1506
+
*/
1507
+
wp.api.init = function( args ) {
1508
+
var endpoint, attributes = {}, deferred, promise;
1509
+
1510
+
args = args || {};
1511
+
attributes.nonce = _.isString( args.nonce ) ? args.nonce : ( wpApiSettings.nonce || '' );
1512
+
attributes.apiRoot = args.apiRoot || wpApiSettings.root || '/wp-json';
1513
+
attributes.versionString = args.versionString || wpApiSettings.versionString || 'wp/v2/';
1514
+
attributes.schema = args.schema || null;
1515
+
attributes.modelEndpoints = args.modelEndpoints || [ 'me', 'settings' ];
1516
+
if ( ! attributes.schema && attributes.apiRoot === wpApiSettings.root && attributes.versionString === wpApiSettings.versionString ) {
1517
+
attributes.schema = wpApiSettings.schema;
1518
+
}
1519
+
1520
+
if ( ! initializedDeferreds[ attributes.apiRoot + attributes.versionString ] ) {
1521
+
1522
+
// Look for an existing copy of this endpoint.
1523
+
endpoint = wp.api.endpoints.findWhere( { 'apiRoot': attributes.apiRoot, 'versionString': attributes.versionString } );
1524
+
if ( ! endpoint ) {
1525
+
endpoint = new Endpoint( attributes );
1526
+
}
1527
+
deferred = jQuery.Deferred();
1528
+
promise = deferred.promise();
1529
+
1530
+
endpoint.schemaConstructed.done( function( resolvedEndpoint ) {
1531
+
wp.api.endpoints.add( resolvedEndpoint );
1532
+
1533
+
// Map the default endpoints, extending any already present items (including Schema model).
1534
+
wp.api.models = _.extend( wp.api.models, resolvedEndpoint.get( 'models' ) );
1535
+
wp.api.collections = _.extend( wp.api.collections, resolvedEndpoint.get( 'collections' ) );
1536
+
deferred.resolve( resolvedEndpoint );
1537
+
} );
1538
+
initializedDeferreds[ attributes.apiRoot + attributes.versionString ] = promise;
1539
+
}
1540
+
return initializedDeferreds[ attributes.apiRoot + attributes.versionString ];
1541
+
};
1542
+
1543
+
/**
1544
+
* Construct the default endpoints and add to an endpoints collection.
1545
+
*/
1546
+
1547
+
// The wp.api.init function returns a promise that will resolve with the endpoint once it is ready.
1548
+
wp.api.loadPromise = wp.api.init();
1549
+
1550
+
} )();
1551
+