105 wp.api.utils.parseISO8601 = function( date ) { |
105 wp.api.utils.parseISO8601 = function( date ) { |
106 var timestamp, struct, i, k, |
106 var timestamp, struct, i, k, |
107 minutesOffset = 0, |
107 minutesOffset = 0, |
108 numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; |
108 numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; |
109 |
109 |
110 // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string |
110 /* |
111 // before falling back to any implementation-specific date parsing, so that’s what we do, even if native |
111 * ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string |
112 // implementations could be faster. |
112 * before falling back to any implementation-specific date parsing, so that’s what we do, even if native |
|
113 * implementations could be faster. |
|
114 */ |
113 // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm |
115 // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm |
114 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 ) ) ) { |
116 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 ) ) ) { |
115 |
117 |
116 // Avoid NaN timestamps caused by “undefined” values being passed to Date.UTC. |
118 // Avoid NaN timestamps caused by “undefined” values being passed to Date.UTC. |
117 for ( i = 0; ( k = numericKeys[i] ); ++i ) { |
119 for ( i = 0; ( k = numericKeys[i] ); ++i ) { |
182 |
184 |
183 /** |
185 /** |
184 * Extract a route part based on negative index. |
186 * Extract a route part based on negative index. |
185 * |
187 * |
186 * @param {string} route The endpoint route. |
188 * @param {string} route The endpoint route. |
187 * @param {int} part The number of parts from the end of the route to retrieve. Default 1. |
189 * @param {number} part The number of parts from the end of the route to retrieve. Default 1. |
188 * Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`. |
190 * Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`. |
189 * @param {string} [versionString] Version string, defaults to `wp.api.versionString`. |
191 * @param {string} [versionString] Version string, defaults to `wp.api.versionString`. |
190 * @param {boolean} [reverse] Whether to reverse the order when extracting the route part. Optional, default false. |
192 * @param {boolean} [reverse] Whether to reverse the order when extracting the route part. Optional, default false. |
191 */ |
193 */ |
192 wp.api.utils.extractRoutePart = function( route, part, versionString, reverse ) { |
194 wp.api.utils.extractRoutePart = function( route, part, versionString, reverse ) { |
230 }; |
232 }; |
231 |
233 |
232 /** |
234 /** |
233 * Add args and options to a model prototype from a route's endpoints. |
235 * Add args and options to a model prototype from a route's endpoints. |
234 * |
236 * |
235 * @param {array} routeEndpoints Array of route endpoints. |
237 * @param {Array} routeEndpoints Array of route endpoints. |
236 * @param {Object} modelInstance An instance of the model (or collection) |
238 * @param {Object} modelInstance An instance of the model (or collection) |
237 * to add the args to. |
239 * to add the args to. |
238 */ |
240 */ |
239 wp.api.utils.decorateFromRoute = function( routeEndpoints, modelInstance ) { |
241 wp.api.utils.decorateFromRoute = function( routeEndpoints, modelInstance ) { |
240 |
242 |
244 _.each( routeEndpoints, function( routeEndpoint ) { |
246 _.each( routeEndpoints, function( routeEndpoint ) { |
245 |
247 |
246 // Add post and edit endpoints as model args. |
248 // Add post and edit endpoints as model args. |
247 if ( _.includes( routeEndpoint.methods, 'POST' ) || _.includes( routeEndpoint.methods, 'PUT' ) ) { |
249 if ( _.includes( routeEndpoint.methods, 'POST' ) || _.includes( routeEndpoint.methods, 'PUT' ) ) { |
248 |
250 |
249 // Add any non empty args, merging them into the args object. |
251 // Add any non-empty args, merging them into the args object. |
250 if ( ! _.isEmpty( routeEndpoint.args ) ) { |
252 if ( ! _.isEmpty( routeEndpoint.args ) ) { |
251 |
253 |
252 // Set as default if no args yet. |
254 // Set as default if no args yet. |
253 if ( _.isEmpty( modelInstance.prototype.args ) ) { |
255 if ( _.isEmpty( modelInstance.prototype.args ) ) { |
254 modelInstance.prototype.args = routeEndpoint.args; |
256 modelInstance.prototype.args = routeEndpoint.args; |
261 } else { |
263 } else { |
262 |
264 |
263 // Add GET method as model options. |
265 // Add GET method as model options. |
264 if ( _.includes( routeEndpoint.methods, 'GET' ) ) { |
266 if ( _.includes( routeEndpoint.methods, 'GET' ) ) { |
265 |
267 |
266 // Add any non empty args, merging them into the defaults object. |
268 // Add any non-empty args, merging them into the defaults object. |
267 if ( ! _.isEmpty( routeEndpoint.args ) ) { |
269 if ( ! _.isEmpty( routeEndpoint.args ) ) { |
268 |
270 |
269 // Set as default if no defaults yet. |
271 // Set as default if no defaults yet. |
270 if ( _.isEmpty( modelInstance.prototype.options ) ) { |
272 if ( _.isEmpty( modelInstance.prototype.options ) ) { |
271 modelInstance.prototype.options = routeEndpoint.args; |
273 modelInstance.prototype.options = routeEndpoint.args; |
323 * or 'date_modified_gmt'. Optional, defaults to 'date'. |
325 * or 'date_modified_gmt'. Optional, defaults to 'date'. |
324 */ |
326 */ |
325 setDate: function( date, field ) { |
327 setDate: function( date, field ) { |
326 var theField = field || 'date'; |
328 var theField = field || 'date'; |
327 |
329 |
328 // Don't alter non parsable date fields. |
330 // Don't alter non-parsable date fields. |
329 if ( _.indexOf( parseableDates, theField ) < 0 ) { |
331 if ( _.indexOf( parseableDates, theField ) < 0 ) { |
330 return false; |
332 return false; |
331 } |
333 } |
332 |
334 |
333 this.set( theField, date.toISOString() ); |
335 this.set( theField, date.toISOString() ); |
344 */ |
346 */ |
345 getDate: function( field ) { |
347 getDate: function( field ) { |
346 var theField = field || 'date', |
348 var theField = field || 'date', |
347 theISODate = this.get( theField ); |
349 theISODate = this.get( theField ); |
348 |
350 |
349 // Only get date fields and non null values. |
351 // Only get date fields and non-null values. |
350 if ( _.indexOf( parseableDates, theField ) < 0 || _.isNull( theISODate ) ) { |
352 if ( _.indexOf( parseableDates, theField ) < 0 || _.isNull( theISODate ) ) { |
351 return false; |
353 return false; |
352 } |
354 } |
353 |
355 |
354 return new Date( wp.api.utils.parseISO8601( theISODate ) ); |
356 return new Date( wp.api.utils.parseISO8601( theISODate ) ); |
356 }, |
358 }, |
357 |
359 |
358 /** |
360 /** |
359 * Build a helper function to retrieve related model. |
361 * Build a helper function to retrieve related model. |
360 * |
362 * |
361 * @param {string} parentModel The parent model. |
363 * @param {string} parentModel The parent model. |
362 * @param {int} modelId The model ID if the object to request |
364 * @param {number} modelId The model ID if the object to request |
363 * @param {string} modelName The model name to use when constructing the model. |
365 * @param {string} modelName The model name to use when constructing the model. |
364 * @param {string} embedSourcePoint Where to check the embedds object for _embed data. |
366 * @param {string} embedSourcePoint Where to check the embedds object for _embed data. |
365 * @param {string} embedCheckField Which model field to check to see if the model has data. |
367 * @param {string} embedCheckField Which model field to check to see if the model has data. |
366 * |
368 * |
367 * @return {Deferred.promise} A promise which resolves to the constructed model. |
369 * @return {Deferred.promise} A promise which resolves to the constructed model. |
368 */ |
370 */ |
369 buildModelGetter = function( parentModel, modelId, modelName, embedSourcePoint, embedCheckField ) { |
371 buildModelGetter = function( parentModel, modelId, modelName, embedSourcePoint, embedCheckField ) { |
370 var getModel, embeddeds, attributes, deferred; |
372 var getModel, embeddeds, attributes, deferred; |
410 }, |
412 }, |
411 |
413 |
412 /** |
414 /** |
413 * Build a helper to retrieve a collection. |
415 * Build a helper to retrieve a collection. |
414 * |
416 * |
415 * @param {string} parentModel The parent model. |
417 * @param {string} parentModel The parent model. |
416 * @param {string} collectionName The name to use when constructing the collection. |
418 * @param {string} collectionName The name to use when constructing the collection. |
417 * @param {string} embedSourcePoint Where to check the embedds object for _embed data. |
419 * @param {string} embedSourcePoint Where to check the embedds object for _embed data. |
418 * @param {string} embedIndex An addiitonal optional index for the _embed data. |
420 * @param {string} embedIndex An addiitonal optional index for the _embed data. |
419 * |
421 * |
420 * @return {Deferred.promise} A promise which resolves to the constructed collection. |
422 * @return {Deferred.promise} A promise which resolves to the constructed collection. |
421 */ |
423 */ |
422 buildCollectionGetter = function( parentModel, collectionName, embedSourcePoint, embedIndex ) { |
424 buildCollectionGetter = function( parentModel, collectionName, embedSourcePoint, embedIndex ) { |
423 /** |
425 /** |
424 * Returns a promise that resolves to the requested collection |
426 * Returns a promise that resolves to the requested collection |
425 * |
427 * |
435 deferred = jQuery.Deferred(); |
437 deferred = jQuery.Deferred(); |
436 |
438 |
437 postId = parentModel.get( 'id' ); |
439 postId = parentModel.get( 'id' ); |
438 embeddeds = parentModel.get( '_embedded' ) || {}; |
440 embeddeds = parentModel.get( '_embedded' ) || {}; |
439 |
441 |
440 // Verify that we have a valid post id. |
442 // Verify that we have a valid post ID. |
441 if ( ! _.isNumber( postId ) || 0 === postId ) { |
443 if ( ! _.isNumber( postId ) || 0 === postId ) { |
442 deferred.reject(); |
444 deferred.reject(); |
443 return deferred; |
445 return deferred; |
444 } |
446 } |
445 |
447 |
509 /** |
511 /** |
510 * Get meta by key for a post. |
512 * Get meta by key for a post. |
511 * |
513 * |
512 * @param {string} key The meta key. |
514 * @param {string} key The meta key. |
513 * |
515 * |
514 * @return {object} The post meta value. |
516 * @return {Object} The post meta value. |
515 */ |
517 */ |
516 getMeta: function( key ) { |
518 getMeta: function( key ) { |
517 var metas = this.get( 'meta' ); |
519 var metas = this.get( 'meta' ); |
518 return metas[ key ]; |
520 return metas[ key ]; |
519 }, |
521 }, |
520 |
522 |
521 /** |
523 /** |
522 * Get all meta key/values for a post. |
524 * Get all meta key/values for a post. |
523 * |
525 * |
524 * @return {object} The post metas, as a key value pair object. |
526 * @return {Object} The post metas, as a key value pair object. |
525 */ |
527 */ |
526 getMetas: function() { |
528 getMetas: function() { |
527 return this.get( 'meta' ); |
529 return this.get( 'meta' ); |
528 }, |
530 }, |
529 |
531 |
530 /** |
532 /** |
531 * Set a group of meta key/values for a post. |
533 * Set a group of meta key/values for a post. |
532 * |
534 * |
533 * @param {object} meta The post meta to set, as key/value pairs. |
535 * @param {Object} meta The post meta to set, as key/value pairs. |
534 */ |
536 */ |
535 setMetas: function( meta ) { |
537 setMetas: function( meta ) { |
536 var metas = this.get( 'meta' ); |
538 var metas = this.get( 'meta' ); |
537 _.extend( metas, meta ); |
539 _.extend( metas, meta ); |
538 this.set( 'meta', metas ); |
540 this.set( 'meta', metas ); |
540 |
542 |
541 /** |
543 /** |
542 * Set a single meta value for a post, by key. |
544 * Set a single meta value for a post, by key. |
543 * |
545 * |
544 * @param {string} key The meta key. |
546 * @param {string} key The meta key. |
545 * @param {object} value The meta value. |
547 * @param {Object} value The meta value. |
546 */ |
548 */ |
547 setMeta: function( key, value ) { |
549 setMeta: function( key, value ) { |
548 var metas = this.get( 'meta' ); |
550 var metas = this.get( 'meta' ); |
549 metas[ key ] = value; |
551 metas[ key ] = value; |
550 this.set( 'meta', metas ); |
552 this.set( 'meta', metas ); |
631 /** |
633 /** |
632 * Set the tags for a post. |
634 * Set the tags for a post. |
633 * |
635 * |
634 * Accepts a Tags collection. |
636 * Accepts a Tags collection. |
635 * |
637 * |
636 * @param {array|Backbone.Collection} tags The tags to set on the post. |
638 * @param {Array|Backbone.Collection} tags The tags to set on the post. |
637 * |
639 * |
638 */ |
640 */ |
639 setTagsWithCollection: function( tags ) { |
641 setTagsWithCollection: function( tags ) { |
640 |
642 |
641 // Pluck out the category ids. |
643 // Pluck out the category IDs. |
642 this.set( 'tags', tags.pluck( 'id' ) ); |
644 this.set( 'tags', tags.pluck( 'id' ) ); |
643 return this.save(); |
645 return this.save(); |
644 } |
646 } |
645 }, |
647 }, |
646 |
648 |
669 /** |
671 /** |
670 * Set the categories for a post. |
672 * Set the categories for a post. |
671 * |
673 * |
672 * Accepts an array of category slugs, or a Categories collection. |
674 * Accepts an array of category slugs, or a Categories collection. |
673 * |
675 * |
674 * @param {array|Backbone.Collection} categories The categories to set on the post. |
676 * @param {Array|Backbone.Collection} categories The categories to set on the post. |
675 * |
677 * |
676 */ |
678 */ |
677 setCategories: function( categories ) { |
679 setCategories: function( categories ) { |
678 var allCategories, newCategory, |
680 var allCategories, newCategory, |
679 self = this, |
681 self = this, |
716 /** |
718 /** |
717 * Set the categories for a post. |
719 * Set the categories for a post. |
718 * |
720 * |
719 * Accepts Categories collection. |
721 * Accepts Categories collection. |
720 * |
722 * |
721 * @param {array|Backbone.Collection} categories The categories to set on the post. |
723 * @param {Array|Backbone.Collection} categories The categories to set on the post. |
722 * |
724 * |
723 */ |
725 */ |
724 setCategoriesWithCollection: function( categories ) { |
726 setCategoriesWithCollection: function( categories ) { |
725 |
727 |
726 // Pluck out the category ids. |
728 // Pluck out the category IDs. |
727 this.set( 'categories', categories.pluck( 'id' ) ); |
729 this.set( 'categories', categories.pluck( 'id' ) ); |
728 return this.save(); |
730 return this.save(); |
729 } |
731 } |
730 }, |
732 }, |
731 |
733 |
833 * Set nonce header before every Backbone sync. |
835 * Set nonce header before every Backbone sync. |
834 * |
836 * |
835 * @param {string} method. |
837 * @param {string} method. |
836 * @param {Backbone.Model} model. |
838 * @param {Backbone.Model} model. |
837 * @param {{beforeSend}, *} options. |
839 * @param {{beforeSend}, *} options. |
838 * @returns {*}. |
840 * @return {*}. |
839 */ |
841 */ |
840 sync: function( method, model, options ) { |
842 sync: function( method, model, options ) { |
841 var beforeSend; |
843 var beforeSend; |
842 |
844 |
843 options = options || {}; |
845 options = options || {}; |
853 } |
855 } |
854 |
856 |
855 if ( _.isFunction( model.nonce ) && ! _.isEmpty( model.nonce() ) ) { |
857 if ( _.isFunction( model.nonce ) && ! _.isEmpty( model.nonce() ) ) { |
856 beforeSend = options.beforeSend; |
858 beforeSend = options.beforeSend; |
857 |
859 |
858 // @todo enable option for jsonp endpoints |
860 // @todo Enable option for jsonp endpoints. |
859 // options.dataType = 'jsonp'; |
861 // options.dataType = 'jsonp'; |
860 |
862 |
861 // Include the nonce with requests. |
863 // Include the nonce with requests. |
862 options.beforeSend = function( xhr ) { |
864 options.beforeSend = function( xhr ) { |
863 xhr.setRequestHeader( 'X-WP-Nonce', model.nonce() ); |
865 xhr.setRequestHeader( 'X-WP-Nonce', model.nonce() ); |
986 * Set nonce header before every Backbone sync. |
988 * Set nonce header before every Backbone sync. |
987 * |
989 * |
988 * @param {string} method. |
990 * @param {string} method. |
989 * @param {Backbone.Model} model. |
991 * @param {Backbone.Model} model. |
990 * @param {{success}, *} options. |
992 * @param {{success}, *} options. |
991 * @returns {*}. |
993 * @return {*}. |
992 */ |
994 */ |
993 sync: function( method, model, options ) { |
995 sync: function( method, model, options ) { |
994 var beforeSend, success, |
996 var beforeSend, success, |
995 self = this; |
997 self = this; |
996 |
998 |
1087 }, |
1089 }, |
1088 |
1090 |
1089 /** |
1091 /** |
1090 * Returns true if there are more pages of objects available. |
1092 * Returns true if there are more pages of objects available. |
1091 * |
1093 * |
1092 * @returns null|boolean. |
1094 * @return {null|boolean} |
1093 */ |
1095 */ |
1094 hasMore: function() { |
1096 hasMore: function() { |
1095 if ( null === this.state.totalPages || |
1097 if ( null === this.state.totalPages || |
1096 null === this.state.totalObjects || |
1098 null === this.state.totalObjects || |
1097 null === this.state.currentPage ) { |
1099 null === this.state.currentPage ) { |
1338 // Include the array of route endpoints for easy reference. |
1340 // Include the array of route endpoints for easy reference. |
1339 endpoints: modelRoute.route.endpoints |
1341 endpoints: modelRoute.route.endpoints |
1340 } ); |
1342 } ); |
1341 } else { |
1343 } else { |
1342 |
1344 |
1343 // This is a model without a parent in its route |
1345 // This is a model without a parent in its route. |
1344 modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); |
1346 modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); |
1345 modelClassName = mapping.models[ modelClassName ] || modelClassName; |
1347 modelClassName = mapping.models[ modelClassName ] || modelClassName; |
1346 loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { |
1348 loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { |
1347 |
1349 |
1348 // Function that returns a constructed url based on the id. |
1350 // Function that returns a constructed url based on the ID. |
1349 url: function() { |
1351 url: function() { |
1350 var url = routeModel.get( 'apiRoot' ) + |
1352 var url = routeModel.get( 'apiRoot' ) + |
1351 routeModel.get( 'versionString' ) + |
1353 routeModel.get( 'versionString' ) + |
1352 ( ( 'me' === routeName ) ? 'users/me' : routeName ); |
1354 ( ( 'me' === routeName ) ? 'users/me' : routeName ); |
1353 |
1355 |
1491 wp.api.endpoints = new Backbone.Collection(); |
1493 wp.api.endpoints = new Backbone.Collection(); |
1492 |
1494 |
1493 /** |
1495 /** |
1494 * Initialize the wp-api, optionally passing the API root. |
1496 * Initialize the wp-api, optionally passing the API root. |
1495 * |
1497 * |
1496 * @param {object} [args] |
1498 * @param {Object} [args] |
1497 * @param {string} [args.nonce] The nonce. Optional, defaults to wpApiSettings.nonce. |
1499 * @param {string} [args.nonce] The nonce. Optional, defaults to wpApiSettings.nonce. |
1498 * @param {string} [args.apiRoot] The api root. Optional, defaults to wpApiSettings.root. |
1500 * @param {string} [args.apiRoot] The api root. Optional, defaults to wpApiSettings.root. |
1499 * @param {string} [args.versionString] The version string. Optional, defaults to wpApiSettings.root. |
1501 * @param {string} [args.versionString] The version string. Optional, defaults to wpApiSettings.root. |
1500 * @param {object} [args.schema] The schema. Optional, will be fetched from API if not provided. |
1502 * @param {Object} [args.schema] The schema. Optional, will be fetched from API if not provided. |
1501 */ |
1503 */ |
1502 wp.api.init = function( args ) { |
1504 wp.api.init = function( args ) { |
1503 var endpoint, attributes = {}, deferred, promise; |
1505 var endpoint, attributes = {}, deferred, promise; |
1504 |
1506 |
1505 args = args || {}; |
1507 args = args || {}; |
1512 attributes.schema = wpApiSettings.schema; |
1514 attributes.schema = wpApiSettings.schema; |
1513 } |
1515 } |
1514 |
1516 |
1515 if ( ! initializedDeferreds[ attributes.apiRoot + attributes.versionString ] ) { |
1517 if ( ! initializedDeferreds[ attributes.apiRoot + attributes.versionString ] ) { |
1516 |
1518 |
1517 // Look for an existing copy of this endpoint |
1519 // Look for an existing copy of this endpoint. |
1518 endpoint = wp.api.endpoints.findWhere( { 'apiRoot': attributes.apiRoot, 'versionString': attributes.versionString } ); |
1520 endpoint = wp.api.endpoints.findWhere( { 'apiRoot': attributes.apiRoot, 'versionString': attributes.versionString } ); |
1519 if ( ! endpoint ) { |
1521 if ( ! endpoint ) { |
1520 endpoint = new Endpoint( attributes ); |
1522 endpoint = new Endpoint( attributes ); |
1521 } |
1523 } |
1522 deferred = jQuery.Deferred(); |
1524 deferred = jQuery.Deferred(); |