[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/ -> wp-api.js (source)

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


Generated : Fri Oct 9 08:20:33 2026 Cross-referenced by PHPXref