| [ 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 */ 864 if ( -1 === _.indexOf( trashableTypes, this.name ) ) { 865 this.requireForceForDelete = true; 866 } 867 }, 868 869 /** 870 * Set nonce header before every Backbone sync. 871 * 872 * @param {string} method The CRUD method ("create", "read", "update", or "delete") to be performed. 873 * @param {Backbone.Model} model The model to be synced. 874 * @param {{beforeSend}, *} options Additional options for the sync. 875 * @return {*}. 876 */ 877 sync: function( method, model, options ) { 878 var beforeSend; 879 880 options = options || {}; 881 882 // Remove date_gmt if null. 883 if ( _.isNull( model.get( 'date_gmt' ) ) ) { 884 model.unset( 'date_gmt' ); 885 } 886 887 // Remove slug if empty. 888 if ( _.isEmpty( model.get( 'slug' ) ) ) { 889 model.unset( 'slug' ); 890 } 891 892 if ( _.isFunction( model.nonce ) && ! _.isEmpty( model.nonce() ) ) { 893 beforeSend = options.beforeSend; 894 895 // @todo Enable option for jsonp endpoints. 896 // options.dataType = 'jsonp'; 897 898 // Include the nonce with requests. 899 options.beforeSend = function( xhr ) { 900 xhr.setRequestHeader( 'X-WP-Nonce', model.nonce() ); 901 902 if ( beforeSend ) { 903 return beforeSend.apply( this, arguments ); 904 } 905 }; 906 907 // Update the nonce when a new nonce is returned with the response. 908 options.complete = function( xhr ) { 909 var returnedNonce = xhr.getResponseHeader( 'X-WP-Nonce' ); 910 911 if ( returnedNonce && _.isFunction( model.nonce ) && model.nonce() !== returnedNonce ) { 912 model.endpointModel.set( 'nonce', returnedNonce ); 913 } 914 }; 915 } 916 917 // Add '?force=true' to use delete method when required. 918 if ( this.requireForceForDelete && 'delete' === method ) { 919 model.url = model.url() + '?force=true'; 920 } 921 return Backbone.sync( method, model, options ); 922 }, 923 924 /** 925 * Save is only allowed when the PUT OR POST methods are available for the endpoint. 926 * @param {Object} attrs The attributes to save. 927 * @param {Object} options The options for the save operation. 928 * @return {boolean} True if the save was executed, false if not allowed. 929 */ 930 save: function( attrs, options ) { 931 932 // Do we have the put method, then execute the save. 933 if ( _.includes( this.methods, 'PUT' ) || _.includes( this.methods, 'POST' ) ) { 934 935 // Proxy the call to the original save function. 936 return Backbone.Model.prototype.save.call( this, attrs, options ); 937 } else { 938 939 // Otherwise bail, disallowing action. 940 return false; 941 } 942 }, 943 944 /** 945 * Delete is only allowed when the DELETE method is available for the endpoint. 946 * @param {Object} [options] The options for the delete operation. 947 * @return {boolean} True if the delete was executed, false if not allowed. 948 */ 949 destroy: function( options ) { 950 951 // Do we have the DELETE method, then execute the destroy. 952 if ( _.includes( this.methods, 'DELETE' ) ) { 953 954 // Proxy the call to the original save function. 955 return Backbone.Model.prototype.destroy.call( this, options ); 956 } else { 957 958 // Otherwise bail, disallowing action. 959 return false; 960 } 961 } 962 963 } 964 ); 965 966 /** 967 * API Schema model. Contains meta information about the API. 968 */ 969 wp.api.models.Schema = wp.api.WPApiBaseModel.extend( 970 /** @lends Schema.prototype */ 971 { 972 defaults: { 973 _links: {}, 974 namespace: null, 975 routes: {} 976 }, 977 978 initialize: function( attributes, options ) { 979 var model = this; 980 options = options || {}; 981 982 wp.api.WPApiBaseModel.prototype.initialize.call( model, attributes, options ); 983 984 model.apiRoot = options.apiRoot || wpApiSettings.root; 985 model.versionString = options.versionString || wpApiSettings.versionString; 986 }, 987 988 url: function() { 989 return this.apiRoot + this.versionString; 990 } 991 } 992 ); 993 })(); 994 995 /** 996 * Creates the base Backbone collection for WordPress REST API. 997 */ 998 ( function() { 999 1000 'use strict'; 1001 1002 var wpApiSettings = window.wpApiSettings || {}; 1003 1004 /** 1005 * Contains basic collection functionality such as pagination. 1006 */ 1007 wp.api.WPApiBaseCollection = Backbone.Collection.extend( 1008 /** @lends BaseCollection.prototype */ 1009 { 1010 1011 /** 1012 * Setup default state. 1013 * @param {Backbone.Model[]} models The initial array of models. 1014 * @param {Object} [options] The options for the collection. 1015 */ 1016 initialize: function( models, options ) { 1017 this.state = { 1018 data: {}, 1019 currentPage: null, 1020 totalPages: null, 1021 totalObjects: null 1022 }; 1023 if ( _.isUndefined( options ) ) { 1024 this.parent = ''; 1025 } else { 1026 this.parent = options.parent; 1027 } 1028 }, 1029 1030 /** 1031 * Extend Backbone.Collection.sync to add nonce and pagination support. 1032 * 1033 * Set nonce header before every Backbone sync. 1034 * 1035 * @param {string} method The CRUD method ("create", "read", "update", or "delete") to be performed. 1036 * @param {Backbone.Model} model The model to be synced. 1037 * @param {{success}, *} options Additional options for the sync. 1038 * @return {*}. 1039 */ 1040 sync: function( method, model, options ) { 1041 var beforeSend, success, 1042 self = this; 1043 1044 options = options || {}; 1045 1046 if ( _.isFunction( model.nonce ) && ! _.isEmpty( model.nonce() ) ) { 1047 beforeSend = options.beforeSend; 1048 1049 // Include the nonce with requests. 1050 options.beforeSend = function( xhr ) { 1051 xhr.setRequestHeader( 'X-WP-Nonce', model.nonce() ); 1052 1053 if ( beforeSend ) { 1054 return beforeSend.apply( self, arguments ); 1055 } 1056 }; 1057 1058 // Update the nonce when a new nonce is returned with the response. 1059 options.complete = function( xhr ) { 1060 var returnedNonce = xhr.getResponseHeader( 'X-WP-Nonce' ); 1061 1062 if ( returnedNonce && _.isFunction( model.nonce ) && model.nonce() !== returnedNonce ) { 1063 model.endpointModel.set( 'nonce', returnedNonce ); 1064 } 1065 }; 1066 } 1067 1068 // When reading, add pagination data. 1069 if ( 'read' === method ) { 1070 if ( options.data ) { 1071 self.state.data = _.clone( options.data ); 1072 1073 delete self.state.data.page; 1074 } else { 1075 self.state.data = options.data = {}; 1076 } 1077 1078 if ( 'undefined' === typeof options.data.page ) { 1079 self.state.currentPage = null; 1080 self.state.totalPages = null; 1081 self.state.totalObjects = null; 1082 } else { 1083 self.state.currentPage = options.data.page - 1; 1084 } 1085 1086 success = options.success; 1087 options.success = function( data, textStatus, request ) { 1088 if ( ! _.isUndefined( request ) ) { 1089 self.state.totalPages = parseInt( request.getResponseHeader( 'x-wp-totalpages' ), 10 ); 1090 self.state.totalObjects = parseInt( request.getResponseHeader( 'x-wp-total' ), 10 ); 1091 } 1092 1093 if ( null === self.state.currentPage ) { 1094 self.state.currentPage = 1; 1095 } else { 1096 self.state.currentPage++; 1097 } 1098 1099 if ( success ) { 1100 return success.apply( this, arguments ); 1101 } 1102 }; 1103 } 1104 1105 // Continue by calling Backbone's sync. 1106 return Backbone.sync( method, model, options ); 1107 }, 1108 1109 /** 1110 * Fetches the next page of objects if a new page exists. 1111 * 1112 * @param {data: {page}} options An object containing the page number to fetch. If not provided, the next page will be fetched. 1113 * @return {*}. 1114 */ 1115 more: function( options ) { 1116 options = options || {}; 1117 options.data = options.data || {}; 1118 1119 _.extend( options.data, this.state.data ); 1120 1121 if ( 'undefined' === typeof options.data.page ) { 1122 if ( ! this.hasMore() ) { 1123 return false; 1124 } 1125 1126 if ( null === this.state.currentPage || this.state.currentPage <= 1 ) { 1127 options.data.page = 2; 1128 } else { 1129 options.data.page = this.state.currentPage + 1; 1130 } 1131 } 1132 1133 return this.fetch( options ); 1134 }, 1135 1136 /** 1137 * Returns true if there are more pages of objects available. 1138 * 1139 * @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. 1140 */ 1141 hasMore: function() { 1142 if ( null === this.state.totalPages || 1143 null === this.state.totalObjects || 1144 null === this.state.currentPage ) { 1145 return null; 1146 } else { 1147 return ( this.state.currentPage < this.state.totalPages ); 1148 } 1149 } 1150 } 1151 ); 1152 1153 } )(); 1154 1155 /** 1156 * Constructs Backbone models and collections from the WordPress REST API schema. 1157 */ 1158 ( function() { 1159 1160 'use strict'; 1161 1162 var Endpoint, initializedDeferreds = {}, 1163 wpApiSettings = window.wpApiSettings || {}; 1164 1165 /** @namespace wp */ 1166 window.wp = window.wp || {}; 1167 1168 /** @namespace wp.api */ 1169 wp.api = wp.api || {}; 1170 1171 // If wpApiSettings is unavailable, try the default. 1172 if ( _.isEmpty( wpApiSettings ) ) { 1173 wpApiSettings.root = window.location.origin + '/wp-json/'; 1174 } 1175 1176 Endpoint = Backbone.Model.extend(/** @lends Endpoint.prototype */{ 1177 defaults: { 1178 apiRoot: wpApiSettings.root, 1179 versionString: wp.api.versionString, 1180 nonce: null, 1181 schema: null, 1182 models: {}, 1183 collections: {} 1184 }, 1185 1186 /** 1187 * Initialize the Endpoint model. 1188 */ 1189 initialize: function() { 1190 var model = this, deferred; 1191 1192 Backbone.Model.prototype.initialize.apply( model, arguments ); 1193 1194 deferred = jQuery.Deferred(); 1195 model.schemaConstructed = deferred.promise(); 1196 1197 model.schemaModel = new wp.api.models.Schema( null, { 1198 apiRoot: model.get( 'apiRoot' ), 1199 versionString: model.get( 'versionString' ), 1200 nonce: model.get( 'nonce' ) 1201 } ); 1202 1203 // When the model loads, resolve the promise. 1204 model.schemaModel.once( 'change', function() { 1205 model.constructFromSchema(); 1206 deferred.resolve( model ); 1207 } ); 1208 1209 if ( model.get( 'schema' ) ) { 1210 1211 // Use schema supplied as model attribute. 1212 model.schemaModel.set( model.schemaModel.parse( model.get( 'schema' ) ) ); 1213 } else if ( 1214 ! _.isUndefined( sessionStorage ) && 1215 ( _.isUndefined( wpApiSettings.cacheSchema ) || wpApiSettings.cacheSchema ) && 1216 sessionStorage.getItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ) ) 1217 ) { 1218 1219 // Use a cached copy of the schema model if available. 1220 model.schemaModel.set( model.schemaModel.parse( JSON.parse( sessionStorage.getItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ) ) ) ) ); 1221 } else { 1222 model.schemaModel.fetch( { 1223 /** 1224 * When the server returns the schema model data, store the data in a sessionCache so we don't 1225 * have to retrieve it again for this session. Then, construct the models and collections based 1226 * on the schema model data. 1227 * 1228 * @ignore 1229 */ 1230 success: function( newSchemaModel ) { 1231 1232 // Store a copy of the schema model in the session cache if available. 1233 if ( ! _.isUndefined( sessionStorage ) && ( _.isUndefined( wpApiSettings.cacheSchema ) || wpApiSettings.cacheSchema ) ) { 1234 try { 1235 sessionStorage.setItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ), JSON.stringify( newSchemaModel ) ); 1236 } catch ( error ) { 1237 1238 // Fail silently, to avoid errors in Safari private mode. 1239 } 1240 } 1241 }, 1242 1243 // Log the error condition. 1244 error: function( err ) { 1245 window.console.log( err ); 1246 } 1247 } ); 1248 } 1249 }, 1250 1251 constructFromSchema: function() { 1252 var routeModel = this, modelRoutes, collectionRoutes, schemaRoot, loadingObjects, 1253 1254 /** 1255 * Set up the model and collection name mapping options. As the schema is built, the 1256 * model and collection names will be adjusted if they are found in the mapping object. 1257 * 1258 * Localizing a variable wpApiSettings.mapping will override the default mapping options. 1259 * 1260 */ 1261 mapping = wpApiSettings.mapping || { 1262 models: { 1263 'Categories': 'Category', 1264 'Comments': 'Comment', 1265 'Pages': 'Page', 1266 'PagesMeta': 'PageMeta', 1267 'PagesRevisions': 'PageRevision', 1268 'Posts': 'Post', 1269 'PostsCategories': 'PostCategory', 1270 'PostsRevisions': 'PostRevision', 1271 'PostsTags': 'PostTag', 1272 'Schema': 'Schema', 1273 'Statuses': 'Status', 1274 'Tags': 'Tag', 1275 'Taxonomies': 'Taxonomy', 1276 'Types': 'Type', 1277 'Users': 'User' 1278 }, 1279 collections: { 1280 'PagesMeta': 'PageMeta', 1281 'PagesRevisions': 'PageRevisions', 1282 'PostsCategories': 'PostCategories', 1283 'PostsMeta': 'PostMeta', 1284 'PostsRevisions': 'PostRevisions', 1285 'PostsTags': 'PostTags' 1286 } 1287 }, 1288 1289 modelEndpoints = routeModel.get( 'modelEndpoints' ), 1290 modelRegex = new RegExp( '(?:.*[+)]|\/(' + modelEndpoints.join( '|' ) + '))$' ); 1291 1292 /** 1293 * Iterate through the routes, picking up models and collections to build. Builds two arrays, 1294 * one for models and one for collections. 1295 */ 1296 modelRoutes = []; 1297 collectionRoutes = []; 1298 schemaRoot = routeModel.get( 'apiRoot' ).replace( wp.api.utils.getRootUrl(), '' ); 1299 loadingObjects = {}; 1300 1301 /** 1302 * Tracking objects for models and collections. 1303 */ 1304 loadingObjects.models = {}; 1305 loadingObjects.collections = {}; 1306 1307 _.each( routeModel.schemaModel.get( 'routes' ), function( route, index ) { 1308 1309 // Skip the schema root if included in the schema. 1310 if ( index !== routeModel.get( ' versionString' ) && 1311 index !== schemaRoot && 1312 index !== ( '/' + routeModel.get( 'versionString' ).slice( 0, -1 ) ) 1313 ) { 1314 1315 // Single items end with a regex, or a special case word. 1316 if ( modelRegex.test( index ) ) { 1317 modelRoutes.push( { index: index, route: route } ); 1318 } else { 1319 1320 // Collections end in a name. 1321 collectionRoutes.push( { index: index, route: route } ); 1322 } 1323 } 1324 } ); 1325 1326 /** 1327 * Construct the models. 1328 * 1329 * Base the class name on the route endpoint. 1330 */ 1331 _.each( modelRoutes, function( modelRoute ) { 1332 1333 // Extract the name and any parent from the route. 1334 var modelClassName, 1335 routeName = wp.api.utils.extractRoutePart( modelRoute.index, 2, routeModel.get( 'versionString' ), true ), 1336 parentName = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), false ), 1337 routeEnd = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), true ); 1338 1339 // Clear the parent part of the route if it is actually the version string. 1340 if ( parentName === routeModel.get( 'versionString' ) ) { 1341 parentName = ''; 1342 } 1343 1344 // Handle the special case of the 'me' route. 1345 if ( 'me' === routeEnd ) { 1346 routeName = 'me'; 1347 } 1348 1349 // If the model has a parent in its route, add that to its class name. 1350 if ( '' !== parentName && parentName !== routeName ) { 1351 modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); 1352 modelClassName = mapping.models[ modelClassName ] || modelClassName; 1353 loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { 1354 1355 // Return a constructed URL based on the parent and ID. 1356 url: function() { 1357 var url = 1358 routeModel.get( 'apiRoot' ) + 1359 routeModel.get( 'versionString' ) + 1360 parentName + '/' + 1361 ( ( _.isUndefined( this.get( 'parent' ) ) || 0 === this.get( 'parent' ) ) ? 1362 ( _.isUndefined( this.get( 'parent_post' ) ) ? '' : this.get( 'parent_post' ) + '/' ) : 1363 this.get( 'parent' ) + '/' ) + 1364 routeName; 1365 1366 if ( ! _.isUndefined( this.get( 'id' ) ) ) { 1367 url += '/' + this.get( 'id' ); 1368 } 1369 return url; 1370 }, 1371 1372 // Track nonces on the Endpoint 'routeModel'. 1373 nonce: function() { 1374 return routeModel.get( 'nonce' ); 1375 }, 1376 1377 endpointModel: routeModel, 1378 1379 // Include a reference to the original route object. 1380 route: modelRoute, 1381 1382 // Include a reference to the original class name. 1383 name: modelClassName, 1384 1385 // Include the array of route methods for easy reference. 1386 methods: modelRoute.route.methods, 1387 1388 // Include the array of route endpoints for easy reference. 1389 endpoints: modelRoute.route.endpoints 1390 } ); 1391 } else { 1392 1393 // This is a model without a parent in its route. 1394 modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); 1395 modelClassName = mapping.models[ modelClassName ] || modelClassName; 1396 loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { 1397 1398 // Function that returns a constructed URL based on the ID. 1399 url: function() { 1400 var url = routeModel.get( 'apiRoot' ) + 1401 routeModel.get( 'versionString' ) + 1402 ( ( 'me' === routeName ) ? 'users/me' : routeName ); 1403 1404 if ( ! _.isUndefined( this.get( 'id' ) ) ) { 1405 url += '/' + this.get( 'id' ); 1406 } 1407 return url; 1408 }, 1409 1410 // Track nonces at the Endpoint level. 1411 nonce: function() { 1412 return routeModel.get( 'nonce' ); 1413 }, 1414 1415 endpointModel: routeModel, 1416 1417 // Include a reference to the original route object. 1418 route: modelRoute, 1419 1420 // Include a reference to the original class name. 1421 name: modelClassName, 1422 1423 // Include the array of route methods for easy reference. 1424 methods: modelRoute.route.methods, 1425 1426 // Include the array of route endpoints for easy reference. 1427 endpoints: modelRoute.route.endpoints 1428 } ); 1429 } 1430 1431 // Add defaults to the new model, pulled form the endpoint. 1432 wp.api.utils.decorateFromRoute( 1433 modelRoute.route.endpoints, 1434 loadingObjects.models[ modelClassName ], 1435 routeModel.get( 'versionString' ) 1436 ); 1437 1438 } ); 1439 1440 /** 1441 * Construct the collections. 1442 * 1443 * Base the class name on the route endpoint. 1444 */ 1445 _.each( collectionRoutes, function( collectionRoute ) { 1446 1447 // Extract the name and any parent from the route. 1448 var collectionClassName, modelClassName, 1449 routeName = collectionRoute.index.slice( collectionRoute.index.lastIndexOf( '/' ) + 1 ), 1450 parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 1, routeModel.get( 'versionString' ), false ); 1451 1452 // If the collection has a parent in its route, add that to its class name. 1453 if ( '' !== parentName && parentName !== routeName && routeModel.get( 'versionString' ) !== parentName ) { 1454 1455 collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); 1456 modelClassName = mapping.models[ collectionClassName ] || collectionClassName; 1457 collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName; 1458 loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( { 1459 1460 // Function that returns a constructed URL passed on the parent. 1461 url: function() { 1462 return routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + 1463 parentName + '/' + 1464 ( ( _.isUndefined( this.parent ) || '' === this.parent ) ? 1465 ( _.isUndefined( this.get( 'parent_post' ) ) ? '' : this.get( 'parent_post' ) + '/' ) : 1466 this.parent + '/' ) + 1467 routeName; 1468 }, 1469 1470 // Specify the model that this collection contains. 1471 model: function( attrs, options ) { 1472 return new loadingObjects.models[ modelClassName ]( attrs, options ); 1473 }, 1474 1475 // Track nonces at the Endpoint level. 1476 nonce: function() { 1477 return routeModel.get( 'nonce' ); 1478 }, 1479 1480 endpointModel: routeModel, 1481 1482 // Include a reference to the original class name. 1483 name: collectionClassName, 1484 1485 // Include a reference to the original route object. 1486 route: collectionRoute, 1487 1488 // Include the array of route methods for easy reference. 1489 methods: collectionRoute.route.methods 1490 } ); 1491 } else { 1492 1493 // This is a collection without a parent in its route. 1494 collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); 1495 modelClassName = mapping.models[ collectionClassName ] || collectionClassName; 1496 collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName; 1497 loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( { 1498 1499 // For the URL of a root level collection, use a string. 1500 url: function() { 1501 return routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + routeName; 1502 }, 1503 1504 // Specify the model that this collection contains. 1505 model: function( attrs, options ) { 1506 return new loadingObjects.models[ modelClassName ]( attrs, options ); 1507 }, 1508 1509 // Track nonces at the Endpoint level. 1510 nonce: function() { 1511 return routeModel.get( 'nonce' ); 1512 }, 1513 1514 endpointModel: routeModel, 1515 1516 // Include a reference to the original class name. 1517 name: collectionClassName, 1518 1519 // Include a reference to the original route object. 1520 route: collectionRoute, 1521 1522 // Include the array of route methods for easy reference. 1523 methods: collectionRoute.route.methods 1524 } ); 1525 } 1526 1527 // Add defaults to the new model, pulled form the endpoint. 1528 wp.api.utils.decorateFromRoute( collectionRoute.route.endpoints, loadingObjects.collections[ collectionClassName ] ); 1529 } ); 1530 1531 // Add mixins and helpers for each of the models. 1532 _.each( loadingObjects.models, function( model, index ) { 1533 loadingObjects.models[ index ] = wp.api.utils.addMixinsAndHelpers( model, index, loadingObjects ); 1534 } ); 1535 1536 // Set the routeModel models and collections. 1537 routeModel.set( 'models', loadingObjects.models ); 1538 routeModel.set( 'collections', loadingObjects.collections ); 1539 1540 } 1541 1542 } ); 1543 1544 wp.api.endpoints = new Backbone.Collection(); 1545 1546 /** 1547 * Initialize the wp-api, optionally passing the API root. 1548 * 1549 * @param {Object} [args] 1550 * @param {string} [args.nonce] The nonce. Optional, defaults to wpApiSettings.nonce. 1551 * @param {string} [args.apiRoot] The api root. Optional, defaults to wpApiSettings.root. 1552 * @param {string} [args.versionString] The version string. Optional, defaults to wpApiSettings.root. 1553 * @param {Object} [args.schema] The schema. Optional, will be fetched from API if not provided. 1554 * @return {Promise} A promise that resolves with the endpoint once it is ready. 1555 */ 1556 wp.api.init = function( args ) { 1557 var endpoint, attributes = {}, deferred, promise; 1558 1559 args = args || {}; 1560 attributes.nonce = _.isString( args.nonce ) ? args.nonce : ( wpApiSettings.nonce || '' ); 1561 attributes.apiRoot = args.apiRoot || wpApiSettings.root || '/wp-json'; 1562 attributes.versionString = args.versionString || wpApiSettings.versionString || 'wp/v2/'; 1563 attributes.schema = args.schema || null; 1564 attributes.modelEndpoints = args.modelEndpoints || [ 'me', 'settings' ]; 1565 if ( ! attributes.schema && attributes.apiRoot === wpApiSettings.root && attributes.versionString === wpApiSettings.versionString ) { 1566 attributes.schema = wpApiSettings.schema; 1567 } 1568 1569 if ( ! initializedDeferreds[ attributes.apiRoot + attributes.versionString ] ) { 1570 1571 // Look for an existing copy of this endpoint. 1572 endpoint = wp.api.endpoints.findWhere( { 'apiRoot': attributes.apiRoot, 'versionString': attributes.versionString } ); 1573 if ( ! endpoint ) { 1574 endpoint = new Endpoint( attributes ); 1575 } 1576 deferred = jQuery.Deferred(); 1577 promise = deferred.promise(); 1578 1579 endpoint.schemaConstructed.done( function( resolvedEndpoint ) { 1580 wp.api.endpoints.add( resolvedEndpoint ); 1581 1582 // Map the default endpoints, extending any already present items (including Schema model). 1583 wp.api.models = _.extend( wp.api.models, resolvedEndpoint.get( 'models' ) ); 1584 wp.api.collections = _.extend( wp.api.collections, resolvedEndpoint.get( 'collections' ) ); 1585 deferred.resolve( resolvedEndpoint ); 1586 } ); 1587 initializedDeferreds[ attributes.apiRoot + attributes.versionString ] = promise; 1588 } 1589 return initializedDeferreds[ attributes.apiRoot + attributes.versionString ]; 1590 }; 1591 1592 /** 1593 * Construct the default endpoints and add to an endpoints collection. 1594 */ 1595 1596 // The wp.api.init function returns a promise that will resolve with the endpoint once it is ready. 1597 wp.api.loadPromise = wp.api.init(); 1598 1599 } )();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Sep 10 08:20:30 2026 | Cross-referenced by PHPXref |