1( function( WP_API_Settings, Backbone, _, window, undefined ) { 2 'use strict'; 3 4 window.wp = window.wp || {}; 5 wp.ccf = wp.ccf || {}; 6 7 wp.ccf.api = { 8 models: {}, 9 collections: {}, 10 utils: {} 11 }; 12 13 /** 14 * ECMAScript 5 shim, from MDN. 15 * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString 16 */ 17 if ( ! Date.prototype.toISOString ) { 18 var pad = function( number ) { 19 var r = String( number ); 20 if ( r.length === 1 ) { 21 r = '0' + r; 22 } 23 24 return r; 25 }; 26 27 Date.prototype.toISOString = function() { 28 return this.getUTCFullYear() + 29 '-' + pad( this.getUTCMonth() + 1 ) + 30 '-' + pad( this.getUTCDate() ) + 31 'T' + pad( this.getUTCHours() ) + 32 ':' + pad( this.getUTCMinutes() ) + 33 ':' + pad( this.getUTCSeconds() ) + 34 '.' + String( ( this.getUTCMilliseconds() / 1000 ).toFixed( 3 ) ).slice( 2, 5 ) + 35 'Z'; 36 }; 37 } 38 39 /** 40 * Parse date into ISO8601 format. 41 * 42 * @param {Date} date. 43 */ 44 wp.ccf.api.utils.parseISO8601 = function( date ) { 45 var timestamp, struct, i, k, 46 minutesOffset = 0, 47 numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; 48 49 // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string 50 // before falling back to any implementation-specific date parsing, so thatâs what we do, even if native 51 // implementations could be faster. 52 // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm 53 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 ) ) ) { 54 // Avoid NaN timestamps caused by âundefinedâ values being passed to Date.UTC. 55 for ( i = 0; ( k = numericKeys[i] ); ++i ) { 56 struct[k] = +struct[k] || 0; 57 } 58 59 // Allow undefined days and months. 60 struct[2] = ( +struct[2] || 1 ) - 1; 61 struct[3] = +struct[3] || 1; 62 63 if ( struct[8] !== 'Z' && struct[9] !== undefined ) { 64 minutesOffset = struct[10] * 60 + struct[11]; 65 66 if ( struct[9] === '+' ) { 67 minutesOffset = 0 - minutesOffset; 68 } 69 } 70 71 timestamp = Date.UTC( struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7] ); 72 } else { 73 timestamp = Date.parse ? Date.parse( date ) : NaN; 74 } 75 76 return timestamp; 77 }; 78 79 /** 80 * Array of parseable dates. 81 * 82 * @type {string[]}. 83 */ 84 var parseable_dates = [ 'date', 'modified', 'date_gmt', 'modified_gmt' ]; 85 86 /** 87 * Mixin for all content that is time stamped. 88 * 89 * @type {{toJSON: toJSON, parse: parse}}. 90 */ 91 var TimeStampedMixin = { 92 /** 93 * Serialize the entity pre-sync. 94 * 95 * @returns {*}. 96 */ 97 toJSON: function() { 98 var attributes = _.clone( this.attributes ); 99 100 // Serialize Date objects back into 8601 strings. 101 _.each( parseable_dates, function( key ) { 102 if ( key in attributes ) { 103 attributes[key] = attributes[key].toISOString(); 104 } 105 }); 106 107 return attributes; 108 }, 109 110 /** 111 * Unserialize the fetched response. 112 * 113 * @param {*} response. 114 * @returns {*}. 115 */ 116 parse: function( response ) { 117 118 // Parse dates into native Date objects. 119 _.each( parseable_dates, function ( key ) { 120 if ( ! ( key in response ) ) { 121 return; 122 } 123 124 var timestamp = wp.ccf.api.utils.parseISO8601( response[key] ); 125 response[key] = new Date( timestamp ); 126 }); 127 128 // Parse the author into a User object. 129 if ( 'undefined' !== typeof response.author ) { 130 response.author = new wp.ccf.api.models.User( response.author ); 131 } 132 133 return response; 134 } 135 }; 136 137 /** 138 * Mixin for all hierarchical content types such as posts. 139 * 140 * @type {{parent: parent}}. 141 */ 142 var HierarchicalMixin = { 143 /** 144 * Get parent object. 145 * 146 * @returns {Backbone.Model} 147 */ 148 parent: function() { 149 150 var object, parent = this.get( 'parent' ); 151 152 // Return null if we don't have a parent. 153 if ( parent === 0 ) { 154 return null; 155 } 156 157 var parentModel = this; 158 159 if ( 'undefined' !== typeof this.parentModel ) { 160 /** 161 * Probably a better way to do this. Perhaps grab a cached version of the 162 * instantiated model? 163 */ 164 parentModel = new this.parentModel(); 165 } 166 167 // Can we get this from its collection? 168 if ( parentModel.collection ) { 169 return parentModel.collection.get( parent ); 170 } else { 171 172 // Otherwise, get the object directly. 173 object = new parentModel.constructor( { 174 id: parent 175 }); 176 177 // Note that this acts asynchronously. 178 object.fetch(); 179 180 return object; 181 } 182 } 183 }; 184 185 /** 186 * Private Backbone base model for all models. 187 */ 188 var BaseModel = Backbone.Model.extend( 189 /** @lends BaseModel.prototype */ 190 { 191 /** 192 * Set nonce header before every Backbone sync. 193 * 194 * @param {string} method. 195 * @param {Backbone.Model} model. 196 * @param {{beforeSend}, *} options. 197 * @returns {*}. 198 */ 199 sync: function( method, model, options ) { 200 options = options || {}; 201 202 if ( 'undefined' !== typeof WP_API_Settings.nonce ) { 203 var beforeSend = options.beforeSend; 204 205 options.beforeSend = function( xhr ) { 206 xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce ); 207 208 if ( beforeSend ) { 209 return beforeSend.apply( this, arguments ); 210 } 211 }; 212 } 213 214 return Backbone.sync( method, model, options ); 215 } 216 } 217 ); 218 219 /** 220 * Backbone model for single users. 221 */ 222 wp.ccf.api.models.User = BaseModel.extend( 223 /** @lends User.prototype */ 224 { 225 idAttribute: 'id', 226 227 urlRoot: WP_API_Settings.root + 'wp/v2/users', 228 229 defaults: { 230 id: null, 231 avatar_url: {}, 232 capabilities: {}, 233 description: '', 234 email: '', 235 extra_capabilities: {}, 236 first_name: '', 237 last_name: '', 238 link: '', 239 name: '', 240 nickname: '', 241 registered_date: new Date(), 242 roles: [], 243 slug: '', 244 url: '', 245 username: '', 246 _links: {} 247 } 248 } 249 ); 250 251 /** 252 * Model for Taxonomy. 253 */ 254 wp.ccf.api.models.Taxonomy = BaseModel.extend( 255 /** @lends Taxonomy.prototype */ 256 { 257 idAttribute: 'slug', 258 259 urlRoot: WP_API_Settings.root + 'wp/v2/taxonomies', 260 261 defaults: { 262 name: '', 263 slug: null, 264 description: '', 265 labels: {}, 266 types: [],
267 show_cloud: false, 268 hierarchical: false 269 } 270 } 271 ); 272 273 /** 274 * Backbone model for term. 275 */ 276 wp.ccf.api.models.Term = BaseModel.extend( 277 /** @lends Term.prototype */ 278 { 279 idAttribute: 'id', 280 281 /** 282 * Return URL for the model. 283 * 284 * @returns {string} 285 */ 286 url: function() { 287 var id = this.get( 'id' ); 288 id = id || ''; 289 290 return WP_API_Settings.root + 'wp/v2/taxonomies/' + this.get( 'taxonomy' ) + '/terms/' + id; 291 }, 292 293 defaults: { 294 id: null, 295 name: '', 296 slug: '', 297 description: '', 298 parent: null, 299 count: 0, 300 link: '', 301 taxonomy: '', 302 _links: {} 303 } 304 305 } 306 ); 307 308 /** 309 * Backbone model for single posts. 310 */ 311 wp.ccf.api.models.Post = BaseModel.extend( _.extend( 312 /** @lends Post.prototype */ 313 { 314 idAttribute: 'id', 315 316 urlRoot: WP_API_Settings.root + 'wp/v2/posts', 317 318 defaults: { 319 id: null, 320 date: new Date(), 321 date_gmt: new Date(), 322 guid: {}, 323 link: '', 324 modified: new Date(), 325 modified_gmt: new Date(), 326 password: '', 327 slug: '', 328 status: 'draft', 329 type: 'post', 330 title: {}, 331 content: {}, 332 author: null, 333 excerpt: {}, 334 featured_image: null, 335 comment_status: 'open', 336 ping_status: 'open', 337 sticky: false, 338 format: 'standard', 339 _links: {} 340 } 341 }, TimeStampedMixin, HierarchicalMixin ) 342 ); 343 344 /** 345 * Backbone model for pages. 346 */ 347 wp.ccf.api.models.Page = BaseModel.extend( _.extend( 348 /** @lends Page.prototype */ 349 { 350 idAttribute: 'id', 351 352 urlRoot: WP_API_Settings.root + 'wp/v2/pages', 353 354 defaults: { 355 id: null, 356 date: new Date(), 357 date_gmt: new Date(), 358 guid: {}, 359 link: '', 360 modified: new Date(), 361 modified_gmt: new Date(), 362 password: '', 363 slug: '', 364 status: 'draft', 365 type: 'page', 366 title: {}, 367 content: {}, 368 author: null, 369 excerpt: {}, 370 featured_image: null, 371 comment_status: 'closed', 372 ping_status: 'closed', 373 menu_order: null, 374 template: '', 375 _links: {} 376 } 377 }, TimeStampedMixin, HierarchicalMixin ) 378 ); 379 380 /** 381 * Backbone model for revisions. 382 */ 383 wp.ccf.api.models.Revision = BaseModel.extend( _.extend( 384 /** @lends Revision.prototype */ 385 { 386 idAttribute: 'id', 387 388 /** 389 * Return URL for the model. 390 * 391 * @returns {string}. 392 */ 393 url: function() { 394 var id = this.get( 'id' ) || ''; 395 396 return WP_API_Settings.root + 'wp/v2/posts/' + id + '/revisions'; 397 }, 398 399 defaults: { 400 id: null, 401 author: null, 402 date: new Date(), 403 date_gmt: new Date(), 404 guid: {}, 405 modified: new Date(), 406 modified_gmt: new Date(), 407 parent: 0, 408 slug: '', 409 title: {}, 410 content: {}, 411 excerpt: {}, 412 _links: {} 413 } 414 415 }, TimeStampedMixin, HierarchicalMixin ) 416 ); 417 418 /** 419 * Backbone model for media items. 420 */ 421 wp.ccf.api.models.Media = BaseModel.extend( _.extend( 422 /** @lends Media.prototype */ 423 { 424 idAttribute: 'id', 425 426 urlRoot: WP_API_Settings.root + 'wp/v2/media', 427 428 defaults: { 429 id: null, 430 date: new Date(), 431 date_gmt: new Date(), 432 guid: {}, 433 link: '', 434 modified: new Date(), 435 modified_gmt: new Date(), 436 password: '', 437 slug: '', 438 status: 'draft', 439 type: 'attachment', 440 title: {}, 441 author: null, 442 comment_status: 'open', 443 ping_status: 'open', 444 alt_text: '', 445 caption: '', 446 description: '', 447 media_type: '', 448 media_details: {}, 449 post: null, 450 source_url: '', 451 _links: {} 452 }, 453 454 /** 455 * @class Represent a media item. 456 * @augments Backbone.Model. 457 * @constructs 458 */ 459 initialize: function() { 460 461 // Todo: what of the parent model is a page? 462 this.parentModel = wp.ccf.api.models.Post; 463 } 464 }, TimeStampedMixin, HierarchicalMixin ) 465 ); 466 467 /** 468 * Backbone model for comments. 469 */ 470 wp.ccf.api.models.Comment = BaseModel.extend( _.extend( 471 /** @lends Comment.prototype */ 472 { 473 idAttribute: 'id', 474 475 defaults: { 476 id: null, 477 author: null, 478 author_email: '', 479 author_ip: '', 480 author_name: '', 481 author_url: '', 482 author_user_agent: '', 483 content: {}, 484 date: new Date(), 485 date_gmt: new Date(), 486 karma: 0, 487 link: '', 488 parent: 0, 489 post: null, 490 status: 'hold', 491 type: '', 492 _links: {} 493 }, 494 495 /** 496 * Return URL for model. 497 * 498 * @returns {string}.
499 */ 500 url: function() { 501 var post_id = this.get( 'post' ); 502 post_id = post_id || ''; 503 504 var id = this.get( 'id' ); 505 id = id || ''; 506 507 return WP_API_Settings.root + 'wp/v2/posts/' + post_id + '/comments/' + id; 508 } 509 }, TimeStampedMixin, HierarchicalMixin ) 510 ); 511 512 /** 513 * Backbone model for single post types. 514 */ 515 wp.ccf.api.models.PostType = BaseModel.extend( 516 /** @lends PostType.prototype */ 517 { 518 idAttribute: 'slug', 519 520 urlRoot: WP_API_Settings.root + 'wp/v2/posts/types', 521 522 defaults: { 523 slug: null, 524 name: '', 525 description: '', 526 labels: {}, 527 hierarchical: false 528 }, 529 530 /** 531 * Prevent model from being saved. 532 * 533 * @returns {boolean}. 534 */ 535 save: function() { 536 return false; 537 }, 538 539 /** 540 * Prevent model from being deleted. 541 * 542 * @returns {boolean}. 543 */ 544 'delete': function() { 545 return false; 546 } 547 } 548 ); 549 550 /** 551 * Backbone model for a post status. 552 */ 553 wp.ccf.api.models.PostStatus = BaseModel.extend( 554 /** @lends PostStatus.prototype */ 555 { 556 idAttribute: 'slug', 557 558 urlRoot: WP_API_Settings.root + 'wp/v2/posts/statuses', 559 560 defaults: { 561 slug: null, 562 name: '', 563 'public': true, 564 'protected': false, 565 'private': false, 566 queryable: true, 567 show_in_list: true, 568 _links: {} 569 }, 570 571 /** 572 * Prevent model from being saved. 573 * 574 * @returns {boolean}. 575 */ 576 save: function() { 577 return false; 578 }, 579 580 /** 581 * Prevent model from being deleted. 582 * 583 * @returns {boolean}. 584 */ 585 'delete': function() { 586 return false; 587 } 588 } 589 ); 590 591 /** 592 * Contains basic collection functionality such as pagination. 593 */ 594 var BaseCollection = Backbone.Collection.extend( 595 /** @lends BaseCollection.prototype */ 596 { 597 598 /** 599 * Setup default state. 600 */ 601 initialize: function() { 602 this.state = { 603 data: {}, 604 currentPage: null, 605 totalPages: null, 606 totalObjects: null 607 }; 608 }, 609 610 /** 611 * Overwrite Backbone.Collection.sync to pagination state based on response headers. 612 * 613 * Set nonce header before every Backbone sync. 614 * 615 * @param {string} method. 616 * @param {Backbone.Model} model. 617 * @param {{success}, *} options. 618 * @returns {*}. 619 */ 620 sync: function( method, model, options ) { 621 options = options || {}; 622 var beforeSend = options.beforeSend, 623 self = this; 624 625 if ( 'undefined' !== typeof WP_API_Settings.nonce ) { 626 options.beforeSend = function( xhr ) { 627 xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce ); 628 629 if ( beforeSend ) { 630 return beforeSend.apply( self, arguments ); 631 } 632 }; 633 } 634 635 if ( 'read' === method ) { 636 if ( options.data ) { 637 self.state.data = _.clone( options.data ); 638 639 delete self.state.data.page; 640 } else { 641 self.state.data = options.data = {}; 642 } 643 644 if ( 'undefined' === typeof options.data.page ) { 645 self.state.currentPage = null; 646 self.state.totalPages = null; 647 self.state.totalObjects = null; 648 } else { 649 self.state.currentPage = options.data.page - 1; 650 } 651 652 var success = options.success; 653 options.success = function( data, textStatus, request ) { 654 self.state.totalPages = parseInt( request.getResponseHeader( 'x-wp-totalpages' ), 10 ); 655 self.state.totalObjects = parseInt( request.getResponseHeader( 'x-wp-total' ), 10 ); 656 657 if ( self.state.currentPage === null ) { 658 self.state.currentPage = 1; 659 } else { 660 self.state.currentPage++; 661 } 662 663 if ( success ) { 664 return success.apply( this, arguments ); 665 } 666 }; 667 } 668 669 return Backbone.sync( method, model, options ); 670 }, 671 672 /** 673 * Fetches the next page of objects if a new page exists. 674 * 675 * @param {data: {page}} options. 676 * @returns {*}. 677 */ 678 more: function( options ) { 679 options = options || {}; 680 options.data = options.data || {}; 681 682 _.extend( options.data, this.state.data ); 683 684 if ( 'undefined' === typeof options.data.page ) { 685 if ( ! this.hasMore() ) { 686 return false; 687 } 688 689 if ( this.state.currentPage === null || this.state.currentPage <= 1 ) { 690 options.data.page = 2; 691 } else { 692 options.data.page = this.state.currentPage + 1; 693 } 694 } 695 696 return this.fetch( options ); 697 }, 698 699 /** 700 * Returns true if there are more pages of objects available. 701 * 702 * @returns null|boolean. 703 */ 704 hasMore: function() { 705 if ( this.state.totalPages === null || 706 this.state.totalObjects === null || 707 this.state.currentPage === null ) { 708 return null; 709 } else { 710 return ( this.state.currentPage < this.state.totalPages ); 711 } 712 } 713 } 714 ); 715 716 /** 717 * Backbone collection for posts. 718 */ 719 wp.ccf.api.collections.Posts = BaseCollection.extend( 720 /** @lends Posts.prototype */ 721 { 722 url: WP_API_Settings.root + 'wp/v2/posts', 723 724 model: wp.ccf.api.models.Post 725 } 726 ); 727 728 /** 729 * Backbone collection for pages. 730 */ 731 wp.ccf.api.collections.Pages = BaseCollection.extend( 732 /** @lends Pages.prototype */ 733 { 734 url: WP_API_Settings.root + 'wp/v2/pages', 735 736 model: wp.ccf.api.models.Page 737 } 738 ); 739 740 /** 741 * Backbone users collection. 742 */ 743 wp.ccf.api.collections.Users = BaseCollection.extend( 744 /** @lends Users.prototype */ 745 { 746 url: WP_API_Settings.root + 'wp/v2/users', 747 748 model: wp.ccf.api.models.User 749 } 750 ); 751 752 /** 753 * Backbone post statuses collection. 754 */ 755 wp.ccf.api.collections.PostStatuses = BaseCollection.extend( 756 /** @lends PostStatuses.prototype */ 757 { 758 url: WP_API_Settings.root + 'wp/v2/statuses', 759 760 model: wp.ccf.api.models.PostStatus, 761 762 parse: function( response ) { 763 var responseArray = []; 764 765 for ( var property in response ) { 766 if ( response.hasOwnProperty( property ) ) { 767 responseArray.push( response[property] ); 768 } 769 } 770 771 return this.constructor.__super__.parse.call( this, responseArray ); 772 } 773 } 774 ); 775 776 /** 777 * Backbone media library collection. 778 */ 779 wp.ccf.api.collections.MediaLibrary = BaseCollection.extend( 780 /** @lends MediaLibrary.prototype */ 781 { 782 url: WP_API_Settings.root + 'wp/v2/media', 783 784 model: wp.ccf.api.models.Media 785 } 786 ); 787 788 /** 789 * Backbone taxonomy collection. 790 */ 791 wp.ccf.api.collections.Taxonomies = BaseCollection.extend( 792 /** @lends Taxonomies.prototype */ 793 { 794 model: wp.ccf.api.models.Taxonomy, 795 796 url: WP_API_Settings.root + 'wp/v2/taxonomies' 797 } 798 ); 799 800 /** 801 * Backbone comment collection. 802 */ 803 wp.ccf.api.collections.Comments = BaseCollection.extend( 804 /** @lends Comments.prototype */ 805 { 806 model: wp.ccf.api.models.Comment, 807 808 /** 809 * Return URL for collection. 810 * 811 * @returns {string}.
812 */ 813 url: WP_API_Settings.root + 'wp/v2/comments' 814 } 815 ); 816 817 /** 818 * Backbone post type collection. 819 */ 820 wp.ccf.api.collections.PostTypes = BaseCollection.extend( 821 /** @lends PostTypes.prototype */ 822 { 823 model: wp.ccf.api.models.PostType, 824 825 url: WP_API_Settings.root + 'wp/v2/types', 826 827 parse: function( response ) { 828 var responseArray = []; 829 830 for ( var property in response ) { 831 if ( response.hasOwnProperty( property ) ) { 832 responseArray.push( response[property] ); 833 } 834 } 835 836 return this.constructor.__super__.parse.call( this, responseArray ); 837 } 838 } 839 ); 840 841 /** 842 * Backbone terms collection. 843 * 844 * Usage: new wp.ccf.api.collections.Terms( {}, { taxonomy: 'taxonomy-slug' } ) 845 */ 846 wp.ccf.api.collections.Terms = BaseCollection.extend( 847 /** @lends Terms.prototype */ 848 { 849 model: wp.ccf.api.models.Term, 850 851 taxonomy: 'category', 852 853 /** 854 * @class Represent an array of terms. 855 * @augments Backbone.Collection. 856 * @constructs 857 */ 858 initialize: function( models, options ) { 859 if ( 'undefined' !== typeof options && options.taxonomy ) { 860 this.taxonomy = options.taxonomy; 861 } 862 863 BaseCollection.prototype.initialize.apply( this, arguments ); 864 }, 865 866 /** 867 * Return URL for collection. 868 * 869 * @returns {string}. 870 */ 871 url: function() { 872 return WP_API_Settings.root + 'wp/v2/terms/' + this.taxonomy; 873 } 874 } 875 ); 876 877 /** 878 * Backbone revisions collection. 879 * 880 * Usage: new wp.ccf.api.collections.Revisions( {}, { parent: POST_ID } ). 881 */ 882 wp.ccf.api.collections.Revisions = BaseCollection.extend( 883 /** @lends Revisions.prototype */ 884 { 885 model: wp.ccf.api.models.Revision, 886 887 parent: null, 888 889 /** 890 * @class Represent an array of revisions. 891 * @augments Backbone.Collection. 892 * @constructs 893 */ 894 initialize: function( models, options ) { 895 BaseCollection.prototype.initialize.apply( this, arguments ); 896 897 if ( options && options.parent ) { 898 this.parent = options.parent; 899 } 900 }, 901 902 /** 903 * return URL for collection. 904 * 905 * @returns {string}. 906 */ 907 url: function() { 908 return WP_API_Settings.root + 'wp/v2/posts/' + this.parent + '/revisions'; 909 } 910 } 911 ); 912 913 /** 914 * Todo: Handle schema endpoints. 915 */ 916 917 /** 918 * Todo: Handle post meta. 919 */ 920 921})( WP_API_Settings, Backbone, _, window, ( void 0 ) );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.