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