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


Generated : Thu Sep 10 08:20:30 2026 Cross-referenced by PHPXref