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


Generated : Sat Sep 19 08:20:30 2026 Cross-referenced by PHPXref