home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / js / wp-api.js < prev    next >
Encoding:
JavaScript  |  2017-10-30  |  44.9 KB  |  1,517 lines

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