Diff: STRATO-apps/wordpress_03/app/wp-includes/js/wp-api.js

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
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 +