Ticket #38373: 38373.diff
File 38373.diff, 783.4 KB (added by , 9 years ago) |
---|
-
src/wp-includes/default-filters.php
374 374 375 375 // REST API actions. 376 376 add_action( 'init', 'rest_api_init' ); 377 add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 ); 377 add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 ); 378 add_action( 'rest_api_init', 'create_initial_rest_routes', 99 ); 378 379 add_action( 'parse_request', 'rest_api_loaded' ); 379 380 380 381 /** -
src/wp-includes/functions.php
3430 3430 } 3431 3431 3432 3432 /** 3433 * Clean up an array, comma- or space-separated list of slugs. 3434 * 3435 * @since 4.7.0 3436 * 3437 * @param array|string $list List of slugs. 3438 * @return array Sanitized array of slugs. 3439 */ 3440 function wp_parse_slug_list( $list ) { 3441 if ( ! is_array( $list ) ) { 3442 $list = preg_split( '/[\s,]+/', $list ); 3443 } 3444 3445 foreach ( $list as $key => $value ) { 3446 $list[ $key ] = sanitize_title( $value ); 3447 } 3448 3449 return array_unique( $list ); 3450 } 3451 3452 /** 3433 3453 * Extract a slice of an array, given a list of keys. 3434 3454 * 3435 3455 * @since 3.1.0 -
src/wp-includes/js/wp-api.js
1 (function( window, undefined ) { 2 3 'use strict'; 4 5 /** 6 * Initialise the WP_API. 7 */ 8 function WP_API() { 9 this.models = {}; 10 this.collections = {}; 11 this.views = {}; 12 } 13 14 window.wp = window.wp || {}; 15 wp.api = wp.api || new WP_API(); 16 wp.api.versionString = wp.api.versionString || 'wp/v2/'; 17 18 // Alias _includes to _.contains, ensuring it is available if lodash is used. 19 if ( ! _.isFunction( _.includes ) && _.isFunction( _.contains ) ) { 20 _.includes = _.contains; 21 } 22 23 })( window ); 24 25 (function( window, undefined ) { 26 27 'use strict'; 28 29 var pad, r; 30 31 window.wp = window.wp || {}; 32 wp.api = wp.api || {}; 33 wp.api.utils = wp.api.utils || {}; 34 35 /** 36 * ECMAScript 5 shim, adapted from MDN. 37 * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString 38 */ 39 if ( ! Date.prototype.toISOString ) { 40 pad = function( number ) { 41 r = String( number ); 42 if ( 1 === r.length ) { 43 r = '0' + r; 44 } 45 46 return r; 47 }; 48 49 Date.prototype.toISOString = function() { 50 return this.getUTCFullYear() + 51 '-' + pad( this.getUTCMonth() + 1 ) + 52 '-' + pad( this.getUTCDate() ) + 53 'T' + pad( this.getUTCHours() ) + 54 ':' + pad( this.getUTCMinutes() ) + 55 ':' + pad( this.getUTCSeconds() ) + 56 '.' + String( ( this.getUTCMilliseconds() / 1000 ).toFixed( 3 ) ).slice( 2, 5 ) + 57 'Z'; 58 }; 59 } 60 61 /** 62 * Parse date into ISO8601 format. 63 * 64 * @param {Date} date. 65 */ 66 wp.api.utils.parseISO8601 = function( date ) { 67 var timestamp, struct, i, k, 68 minutesOffset = 0, 69 numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; 70 71 // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string 72 // before falling back to any implementation-specific date parsing, so that’s what we do, even if native 73 // implementations could be faster. 74 // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm 75 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 ) ) ) { 76 77 // Avoid NaN timestamps caused by “undefined” values being passed to Date.UTC. 78 for ( i = 0; ( k = numericKeys[i] ); ++i ) { 79 struct[k] = +struct[k] || 0; 80 } 81 82 // Allow undefined days and months. 83 struct[2] = ( +struct[2] || 1 ) - 1; 84 struct[3] = +struct[3] || 1; 85 86 if ( 'Z' !== struct[8] && undefined !== struct[9] ) { 87 minutesOffset = struct[10] * 60 + struct[11]; 88 89 if ( '+' === struct[9] ) { 90 minutesOffset = 0 - minutesOffset; 91 } 92 } 93 94 timestamp = Date.UTC( struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7] ); 95 } else { 96 timestamp = Date.parse ? Date.parse( date ) : NaN; 97 } 98 99 return timestamp; 100 }; 101 102 /** 103 * Helper function for getting the root URL. 104 * @return {[type]} [description] 105 */ 106 wp.api.utils.getRootUrl = function() { 107 return window.location.origin ? 108 window.location.origin + '/' : 109 window.location.protocol + '/' + window.location.host + '/'; 110 }; 111 112 /** 113 * Helper for capitalizing strings. 114 */ 115 wp.api.utils.capitalize = function( str ) { 116 if ( _.isUndefined( str ) ) { 117 return str; 118 } 119 return str.charAt( 0 ).toUpperCase() + str.slice( 1 ); 120 }; 121 122 /** 123 * Extract a route part based on negative index. 124 * 125 * @param {string} route The endpoint route. 126 * @param {int} part The number of parts from the end of the route to retrieve. Default 1. 127 * Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`. 128 */ 129 wp.api.utils.extractRoutePart = function( route, part ) { 130 var routeParts; 131 132 part = part || 1; 133 134 // Remove versions string from route to avoid returning it. 135 route = route.replace( wp.api.versionString, '' ); 136 routeParts = route.split( '/' ).reverse(); 137 if ( _.isUndefined( routeParts[ --part ] ) ) { 138 return ''; 139 } 140 return routeParts[ part ]; 141 }; 142 143 /** 144 * Extract a parent name from a passed route. 145 * 146 * @param {string} route The route to extract a name from. 147 */ 148 wp.api.utils.extractParentName = function( route ) { 149 var name, 150 lastSlash = route.lastIndexOf( '_id>[\\d]+)/' ); 151 152 if ( lastSlash < 0 ) { 153 return ''; 154 } 155 name = route.substr( 0, lastSlash - 1 ); 156 name = name.split( '/' ); 157 name.pop(); 158 name = name.pop(); 159 return name; 160 }; 161 162 /** 163 * Add args and options to a model prototype from a route's endpoints. 164 * 165 * @param {array} routeEndpoints Array of route endpoints. 166 * @param {Object} modelInstance An instance of the model (or collection) 167 * to add the args to. 168 */ 169 wp.api.utils.decorateFromRoute = function( routeEndpoints, modelInstance ) { 170 171 /** 172 * Build the args based on route endpoint data. 173 */ 174 _.each( routeEndpoints, function( routeEndpoint ) { 175 176 // Add post and edit endpoints as model args. 177 if ( _.includes( routeEndpoint.methods, 'POST' ) || _.includes( routeEndpoint.methods, 'PUT' ) ) { 178 179 // Add any non empty args, merging them into the args object. 180 if ( ! _.isEmpty( routeEndpoint.args ) ) { 181 182 // Set as defauls if no args yet. 183 if ( _.isEmpty( modelInstance.prototype.args ) ) { 184 modelInstance.prototype.args = routeEndpoint.args; 185 } else { 186 187 // We already have args, merge these new args in. 188 modelInstance.prototype.args = _.union( routeEndpoint.args, modelInstance.prototype.defaults ); 189 } 190 } 191 } else { 192 193 // Add GET method as model options. 194 if ( _.includes( routeEndpoint.methods, 'GET' ) ) { 195 196 // Add any non empty args, merging them into the defaults object. 197 if ( ! _.isEmpty( routeEndpoint.args ) ) { 198 199 // Set as defauls if no defaults yet. 200 if ( _.isEmpty( modelInstance.prototype.options ) ) { 201 modelInstance.prototype.options = routeEndpoint.args; 202 } else { 203 204 // We already have options, merge these new args in. 205 modelInstance.prototype.options = _.union( routeEndpoint.args, modelInstance.prototype.options ); 206 } 207 } 208 209 } 210 } 211 212 } ); 213 214 }; 215 216 /** 217 * Add mixins and helpers to models depending on their defaults. 218 * 219 * @param {Backbone Model} model The model to attach helpers and mixins to. 220 * @param {string} modelClassName The classname of the constructed model. 221 * @param {Object} loadingObjects An object containing the models and collections we are building. 222 */ 223 wp.api.utils.addMixinsAndHelpers = function( model, modelClassName, loadingObjects ) { 224 225 var hasDate = false, 226 227 /** 228 * Array of parseable dates. 229 * 230 * @type {string[]}. 231 */ 232 parseableDates = [ 'date', 'modified', 'date_gmt', 'modified_gmt' ], 233 234 /** 235 * Mixin for all content that is time stamped. 236 * 237 * This mixin converts between mysql timestamps and JavaScript Dates when syncing a model 238 * to or from the server. For example, a date stored as `2015-12-27T21:22:24` on the server 239 * gets expanded to `Sun Dec 27 2015 14:22:24 GMT-0700 (MST)` when the model is fetched. 240 * 241 * @type {{toJSON: toJSON, parse: parse}}. 242 */ 243 TimeStampedMixin = { 244 245 /** 246 * Prepare a JavaScript Date for transmitting to the server. 247 * 248 * This helper function accepts a field and Date object. It converts the passed Date 249 * to an ISO string and sets that on the model field. 250 * 251 * @param {Date} date A JavaScript date object. WordPress expects dates in UTC. 252 * @param {string} field The date field to set. One of 'date', 'date_gmt', 'date_modified' 253 * or 'date_modified_gmt'. Optional, defaults to 'date'. 254 */ 255 setDate: function( date, field ) { 256 var theField = field || 'date'; 257 258 // Don't alter non parsable date fields. 259 if ( _.indexOf( parseableDates, theField ) < 0 ) { 260 return false; 261 } 262 263 this.set( theField, date.toISOString() ); 264 }, 265 266 /** 267 * Get a JavaScript Date from the passed field. 268 * 269 * WordPress returns 'date' and 'date_modified' in the timezone of the server as well as 270 * UTC dates as 'date_gmt' and 'date_modified_gmt'. Draft posts do not include UTC dates. 271 * 272 * @param {string} field The date field to set. One of 'date', 'date_gmt', 'date_modified' 273 * or 'date_modified_gmt'. Optional, defaults to 'date'. 274 */ 275 getDate: function( field ) { 276 var theField = field || 'date', 277 theISODate = this.get( theField ); 278 279 // Only get date fields and non null values. 280 if ( _.indexOf( parseableDates, theField ) < 0 || _.isNull( theISODate ) ) { 281 return false; 282 } 283 284 return new Date( wp.api.utils.parseISO8601( theISODate ) ); 285 } 286 }, 287 288 /** 289 * Build a helper function to retrieve related model. 290 * 291 * @param {string} parentModel The parent model. 292 * @param {int} modelId The model ID if the object to request 293 * @param {string} modelName The model name to use when constructing the model. 294 * @param {string} embedSourcePoint Where to check the embedds object for _embed data. 295 * @param {string} embedCheckField Which model field to check to see if the model has data. 296 * 297 * @return {Deferred.promise} A promise which resolves to the constructed model. 298 */ 299 buildModelGetter = function( parentModel, modelId, modelName, embedSourcePoint, embedCheckField ) { 300 var getModel, embeddeds, attributes, deferred; 301 302 deferred = jQuery.Deferred(); 303 embeddeds = parentModel.get( '_embedded' ) || {}; 304 305 // Verify that we have a valied object id. 306 if ( ! _.isNumber( modelId ) || 0 === modelId ) { 307 deferred.reject(); 308 return deferred; 309 } 310 311 // If we have embedded object data, use that when constructing the getModel. 312 if ( embeddeds[ embedSourcePoint ] ) { 313 attributes = _.findWhere( embeddeds[ embedSourcePoint ], { id: modelId } ); 314 } 315 316 // Otherwise use the modelId. 317 if ( ! attributes ) { 318 attributes = { id: modelId }; 319 } 320 321 // Create the new getModel model. 322 getModel = new wp.api.models[ modelName ]( attributes ); 323 324 // If we didn’t have an embedded getModel, fetch the getModel data. 325 if ( ! getModel.get( embedCheckField ) ) { 326 getModel.fetch( { success: function( getModel ) { 327 deferred.resolve( getModel ); 328 } } ); 329 } else { 330 deferred.resolve( getModel ); 331 } 332 333 // Return a promise. 334 return deferred.promise(); 335 }, 336 337 /** 338 * Build a helper to retrieve a collection. 339 * 340 * @param {string} parentModel The parent model. 341 * @param {string} collectionName The name to use when constructing the collection. 342 * @param {string} embedSourcePoint Where to check the embedds object for _embed data. 343 * @param {string} embedIndex An addiitonal optional index for the _embed data. 344 * 345 * @return {Deferred.promise} A promise which resolves to the constructed collection. 346 */ 347 buildCollectionGetter = function( parentModel, collectionName, embedSourcePoint, embedIndex ) { 348 /** 349 * Returns a promise that resolves to the requested collection 350 * 351 * Uses the embedded data if available, otherwises fetches the 352 * data from the server. 353 * 354 * @return {Deferred.promise} promise Resolves to a wp.api.collections[ collectionName ] 355 * collection. 356 */ 357 var postId, embeddeds, getObjects, 358 classProperties = '', 359 properties = '', 360 deferred = jQuery.Deferred(); 361 362 postId = parentModel.get( 'id' ); 363 embeddeds = parentModel.get( '_embedded' ) || {}; 364 365 // Verify that we have a valied post id. 366 if ( ! _.isNumber( postId ) || 0 === postId ) { 367 deferred.reject(); 368 return deferred; 369 } 370 371 // If we have embedded getObjects data, use that when constructing the getObjects. 372 if ( ! _.isUndefined( embedSourcePoint ) && ! _.isUndefined( embeddeds[ embedSourcePoint ] ) ) { 373 374 // Some embeds also include an index offset, check for that. 375 if ( _.isUndefined( embedIndex ) ) { 376 377 // Use the embed source point directly. 378 properties = embeddeds[ embedSourcePoint ]; 379 } else { 380 381 // Add the index to the embed source point. 382 properties = embeddeds[ embedSourcePoint ][ embedIndex ]; 383 } 384 } else { 385 386 // Otherwise use the postId. 387 classProperties = { parent: postId }; 388 } 389 390 // Create the new getObjects collection. 391 getObjects = new wp.api.collections[ collectionName ]( properties, classProperties ); 392 393 // If we didn’t have embedded getObjects, fetch the getObjects data. 394 if ( _.isUndefined( getObjects.models[0] ) ) { 395 getObjects.fetch( { success: function( getObjects ) { 396 397 // Add a helper 'parent_post' attribute onto the model. 398 setHelperParentPost( getObjects, postId ); 399 deferred.resolve( getObjects ); 400 } } ); 401 } else { 402 403 // Add a helper 'parent_post' attribute onto the model. 404 setHelperParentPost( getObjects, postId ); 405 deferred.resolve( getObjects ); 406 } 407 408 // Return a promise. 409 return deferred.promise(); 410 411 }, 412 413 /** 414 * Set the model post parent. 415 */ 416 setHelperParentPost = function( collection, postId ) { 417 418 // Attach post_parent id to the collection. 419 _.each( collection.models, function( model ) { 420 model.set( 'parent_post', postId ); 421 } ); 422 }, 423 424 /** 425 * Add a helper funtion to handle post Meta. 426 */ 427 MetaMixin = { 428 getMeta: function() { 429 return buildCollectionGetter( this, 'PostMeta', 'https://api.w.org/meta' ); 430 } 431 }, 432 433 /** 434 * Add a helper funtion to handle post Revisions. 435 */ 436 RevisionsMixin = { 437 getRevisions: function() { 438 return buildCollectionGetter( this, 'PostRevisions' ); 439 } 440 }, 441 442 /** 443 * Add a helper funtion to handle post Tags. 444 */ 445 TagsMixin = { 446 447 /** 448 * Get the tags for a post. 449 * 450 * @return {Deferred.promise} promise Resolves to an array of tags. 451 */ 452 getTags: function() { 453 var tagIds = this.get( 'tags' ), 454 tags = new wp.api.collections.Tags(); 455 456 // Resolve with an empty array if no tags. 457 if ( _.isEmpty( tagIds ) ) { 458 return jQuery.Deferred().resolve( [] ); 459 } 460 461 return tags.fetch( { data: { include: tagIds } } ); 462 }, 463 464 /** 465 * Set the tags for a post. 466 * 467 * Accepts an array of tag slugs, or a Tags collection. 468 * 469 * @param {array|Backbone.Collection} tags The tags to set on the post. 470 * 471 */ 472 setTags: function( tags ) { 473 var allTags, newTag, 474 self = this, 475 newTags = []; 476 477 if ( _.isString( tags ) ) { 478 return false; 479 } 480 481 // If this is an array of slugs, build a collection. 482 if ( _.isArray( tags ) ) { 483 484 // Get all the tags. 485 allTags = new wp.api.collections.Tags(); 486 allTags.fetch( { 487 data: { per_page: 100 }, 488 success: function( alltags ) { 489 490 // Find the passed tags and set them up. 491 _.each( tags, function( tag ) { 492 newTag = new wp.api.models.Tag( alltags.findWhere( { slug: tag } ) ); 493 494 // Tie the new tag to the post. 495 newTag.set( 'parent_post', self.get( 'id' ) ); 496 497 // Add the new tag to the collection. 498 newTags.push( newTag ); 499 } ); 500 tags = new wp.api.collections.Tags( newTags ); 501 self.setTagsWithCollection( tags ); 502 } 503 } ); 504 505 } else { 506 this.setTagsWithCollection( tags ); 507 } 508 }, 509 510 /** 511 * Set the tags for a post. 512 * 513 * Accepts a Tags collection. 514 * 515 * @param {array|Backbone.Collection} tags The tags to set on the post. 516 * 517 */ 518 setTagsWithCollection: function( tags ) { 519 520 // Pluck out the category ids. 521 this.set( 'tags', tags.pluck( 'id' ) ); 522 return this.save(); 523 } 524 }, 525 526 /** 527 * Add a helper funtion to handle post Categories. 528 */ 529 CategoriesMixin = { 530 531 /** 532 * Get a the categories for a post. 533 * 534 * @return {Deferred.promise} promise Resolves to an array of categories. 535 */ 536 getCategories: function() { 537 var categoryIds = this.get( 'categories' ), 538 categories = new wp.api.collections.Categories(); 539 540 // Resolve with an empty array if no categories. 541 if ( _.isEmpty( categoryIds ) ) { 542 return jQuery.Deferred().resolve( [] ); 543 } 544 545 return categories.fetch( { data: { include: categoryIds } } ); 546 }, 547 548 /** 549 * Set the categories for a post. 550 * 551 * Accepts an array of category slugs, or a Categories collection. 552 * 553 * @param {array|Backbone.Collection} categories The categories to set on the post. 554 * 555 */ 556 setCategories: function( categories ) { 557 var allCategories, newCategory, 558 self = this, 559 newCategories = []; 560 561 if ( _.isString( categories ) ) { 562 return false; 563 } 564 565 // If this is an array of slugs, build a collection. 566 if ( _.isArray( categories ) ) { 567 568 // Get all the categories. 569 allCategories = new wp.api.collections.Categories(); 570 allCategories.fetch( { 571 data: { per_page: 100 }, 572 success: function( allcats ) { 573 574 // Find the passed categories and set them up. 575 _.each( categories, function( category ) { 576 newCategory = new wp.api.models.Category( allcats.findWhere( { slug: category } ) ); 577 578 // Tie the new category to the post. 579 newCategory.set( 'parent_post', self.get( 'id' ) ); 580 581 // Add the new category to the collection. 582 newCategories.push( newCategory ); 583 } ); 584 categories = new wp.api.collections.Categories( newCategories ); 585 self.setCategoriesWithCollection( categories ); 586 } 587 } ); 588 589 } else { 590 this.setCategoriesWithCollection( categories ); 591 } 592 593 }, 594 595 /** 596 * Set the categories for a post. 597 * 598 * Accepts Categories collection. 599 * 600 * @param {array|Backbone.Collection} categories The categories to set on the post. 601 * 602 */ 603 setCategoriesWithCollection: function( categories ) { 604 605 // Pluck out the category ids. 606 this.set( 'categories', categories.pluck( 'id' ) ); 607 return this.save(); 608 } 609 }, 610 611 /** 612 * Add a helper function to retrieve the author user model. 613 */ 614 AuthorMixin = { 615 getAuthorUser: function() { 616 return buildModelGetter( this, this.get( 'author' ), 'User', 'author', 'name' ); 617 } 618 }, 619 620 /** 621 * Add a helper function to retrieve the featured media. 622 */ 623 FeaturedMediaMixin = { 624 getFeaturedMedia: function() { 625 return buildModelGetter( this, this.get( 'featured_media' ), 'Media', 'wp:featuredmedia', 'source_url' ); 626 } 627 }; 628 629 // Exit if we don't have valid model defaults. 630 if ( _.isUndefined( model.prototype.args ) ) { 631 return model; 632 } 633 634 // Go thru the parsable date fields, if our model contains any of them it gets the TimeStampedMixin. 635 _.each( parseableDates, function( theDateKey ) { 636 if ( ! _.isUndefined( model.prototype.args[ theDateKey ] ) ) { 637 hasDate = true; 638 } 639 } ); 640 641 // Add the TimeStampedMixin for models that contain a date field. 642 if ( hasDate ) { 643 model = model.extend( TimeStampedMixin ); 644 } 645 646 // Add the AuthorMixin for models that contain an author. 647 if ( ! _.isUndefined( model.prototype.args.author ) ) { 648 model = model.extend( AuthorMixin ); 649 } 650 651 // Add the FeaturedMediaMixin for models that contain a featured_media. 652 if ( ! _.isUndefined( model.prototype.args.featured_media ) ) { 653 model = model.extend( FeaturedMediaMixin ); 654 } 655 656 // Add the CategoriesMixin for models that support categories collections. 657 if ( ! _.isUndefined( model.prototype.args.categories ) ) { 658 model = model.extend( CategoriesMixin ); 659 } 660 661 // Add the MetaMixin for models that support meta collections. 662 if ( ! _.isUndefined( loadingObjects.collections[ modelClassName + 'Meta' ] ) ) { 663 model = model.extend( MetaMixin ); 664 } 665 666 // Add the TagsMixin for models that support tags collections. 667 if ( ! _.isUndefined( model.prototype.args.tags ) ) { 668 model = model.extend( TagsMixin ); 669 } 670 671 // Add the RevisionsMixin for models that support revisions collections. 672 if ( ! _.isUndefined( loadingObjects.collections[ modelClassName + 'Revisions' ] ) ) { 673 model = model.extend( RevisionsMixin ); 674 } 675 676 return model; 677 }; 678 679 })( window ); 680 681 /* global wpApiSettings:false */ 682 683 // Suppress warning about parse function's unused "options" argument: 684 /* jshint unused:false */ 685 (function() { 686 687 'use strict'; 688 689 var wpApiSettings = window.wpApiSettings || {}; 690 691 /** 692 * Backbone base model for all models. 693 */ 694 wp.api.WPApiBaseModel = Backbone.Model.extend( 695 /** @lends WPApiBaseModel.prototype */ 696 { 697 /** 698 * Set nonce header before every Backbone sync. 699 * 700 * @param {string} method. 701 * @param {Backbone.Model} model. 702 * @param {{beforeSend}, *} options. 703 * @returns {*}. 704 */ 705 sync: function( method, model, options ) { 706 var beforeSend; 707 708 options = options || {}; 709 710 // Remove date_gmt if null. 711 if ( _.isNull( model.get( 'date_gmt' ) ) ) { 712 model.unset( 'date_gmt' ); 713 } 714 715 // Remove slug if empty. 716 if ( _.isEmpty( model.get( 'slug' ) ) ) { 717 model.unset( 'slug' ); 718 } 719 720 if ( ! _.isUndefined( wpApiSettings.nonce ) && ! _.isNull( wpApiSettings.nonce ) ) { 721 beforeSend = options.beforeSend; 722 723 // @todo enable option for jsonp endpoints 724 // options.dataType = 'jsonp'; 725 726 options.beforeSend = function( xhr ) { 727 xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce ); 728 729 if ( beforeSend ) { 730 return beforeSend.apply( this, arguments ); 731 } 732 }; 733 } 734 735 // Add '?force=true' to use delete method when required. 736 if ( this.requireForceForDelete && 'delete' === method ) { 737 model.url = model.url() + '?force=true'; 738 } 739 return Backbone.sync( method, model, options ); 740 }, 741 742 /** 743 * Save is only allowed when the PUT OR POST methods are available for the endpoint. 744 */ 745 save: function( attrs, options ) { 746 747 // Do we have the put method, then execute the save. 748 if ( _.includes( this.methods, 'PUT' ) || _.includes( this.methods, 'POST' ) ) { 749 750 // Proxy the call to the original save function. 751 return Backbone.Model.prototype.save.call( this, attrs, options ); 752 } else { 753 754 // Otherwise bail, disallowing action. 755 return false; 756 } 757 }, 758 759 /** 760 * Delete is only allowed when the DELETE method is available for the endpoint. 761 */ 762 destroy: function( options ) { 763 764 // Do we have the DELETE method, then execute the destroy. 765 if ( _.includes( this.methods, 'DELETE' ) ) { 766 767 // Proxy the call to the original save function. 768 return Backbone.Model.prototype.destroy.call( this, options ); 769 } else { 770 771 // Otherwise bail, disallowing action. 772 return false; 773 } 774 } 775 776 } 777 ); 778 779 /** 780 * API Schema model. Contains meta information about the API. 781 */ 782 wp.api.models.Schema = wp.api.WPApiBaseModel.extend( 783 /** @lends Schema.prototype */ 784 { 785 defaults: { 786 _links: {}, 787 namespace: null, 788 routes: {} 789 }, 790 791 initialize: function( attributes, options ) { 792 var model = this; 793 options = options || {}; 794 795 wp.api.WPApiBaseModel.prototype.initialize.call( model, attributes, options ); 796 797 model.apiRoot = options.apiRoot || wpApiSettings.root; 798 model.versionString = options.versionString || wpApiSettings.versionString; 799 }, 800 801 url: function() { 802 return this.apiRoot + this.versionString; 803 } 804 } 805 ); 806 })(); 807 808 ( function() { 809 810 'use strict'; 811 812 var wpApiSettings = window.wpApiSettings || {}; 813 814 /** 815 * Contains basic collection functionality such as pagination. 816 */ 817 wp.api.WPApiBaseCollection = Backbone.Collection.extend( 818 /** @lends BaseCollection.prototype */ 819 { 820 821 /** 822 * Setup default state. 823 */ 824 initialize: function( models, options ) { 825 this.state = { 826 data: {}, 827 currentPage: null, 828 totalPages: null, 829 totalObjects: null 830 }; 831 if ( _.isUndefined( options ) ) { 832 this.parent = ''; 833 } else { 834 this.parent = options.parent; 835 } 836 }, 837 838 /** 839 * Extend Backbone.Collection.sync to add nince and pagination support. 840 * 841 * Set nonce header before every Backbone sync. 842 * 843 * @param {string} method. 844 * @param {Backbone.Model} model. 845 * @param {{success}, *} options. 846 * @returns {*}. 847 */ 848 sync: function( method, model, options ) { 849 var beforeSend, success, 850 self = this; 851 852 options = options || {}; 853 beforeSend = options.beforeSend; 854 855 // If we have a localized nonce, pass that along with each sync. 856 if ( 'undefined' !== typeof wpApiSettings.nonce ) { 857 options.beforeSend = function( xhr ) { 858 xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce ); 859 860 if ( beforeSend ) { 861 return beforeSend.apply( self, arguments ); 862 } 863 }; 864 } 865 866 // When reading, add pagination data. 867 if ( 'read' === method ) { 868 if ( options.data ) { 869 self.state.data = _.clone( options.data ); 870 871 delete self.state.data.page; 872 } else { 873 self.state.data = options.data = {}; 874 } 875 876 if ( 'undefined' === typeof options.data.page ) { 877 self.state.currentPage = null; 878 self.state.totalPages = null; 879 self.state.totalObjects = null; 880 } else { 881 self.state.currentPage = options.data.page - 1; 882 } 883 884 success = options.success; 885 options.success = function( data, textStatus, request ) { 886 if ( ! _.isUndefined( request ) ) { 887 self.state.totalPages = parseInt( request.getResponseHeader( 'x-wp-totalpages' ), 10 ); 888 self.state.totalObjects = parseInt( request.getResponseHeader( 'x-wp-total' ), 10 ); 889 } 890 891 if ( null === self.state.currentPage ) { 892 self.state.currentPage = 1; 893 } else { 894 self.state.currentPage++; 895 } 896 897 if ( success ) { 898 return success.apply( this, arguments ); 899 } 900 }; 901 } 902 903 // Continue by calling Bacckbone's sync. 904 return Backbone.sync( method, model, options ); 905 }, 906 907 /** 908 * Fetches the next page of objects if a new page exists. 909 * 910 * @param {data: {page}} options. 911 * @returns {*}. 912 */ 913 more: function( options ) { 914 options = options || {}; 915 options.data = options.data || {}; 916 917 _.extend( options.data, this.state.data ); 918 919 if ( 'undefined' === typeof options.data.page ) { 920 if ( ! this.hasMore() ) { 921 return false; 922 } 923 924 if ( null === this.state.currentPage || this.state.currentPage <= 1 ) { 925 options.data.page = 2; 926 } else { 927 options.data.page = this.state.currentPage + 1; 928 } 929 } 930 931 return this.fetch( options ); 932 }, 933 934 /** 935 * Returns true if there are more pages of objects available. 936 * 937 * @returns null|boolean. 938 */ 939 hasMore: function() { 940 if ( null === this.state.totalPages || 941 null === this.state.totalObjects || 942 null === this.state.currentPage ) { 943 return null; 944 } else { 945 return ( this.state.currentPage < this.state.totalPages ); 946 } 947 } 948 } 949 ); 950 951 } )(); 952 953 ( function() { 954 955 'use strict'; 956 957 var Endpoint, initializedDeferreds = {}, 958 wpApiSettings = window.wpApiSettings || {}; 959 window.wp = window.wp || {}; 960 wp.api = wp.api || {}; 961 962 // If wpApiSettings is unavailable, try the default. 963 if ( _.isEmpty( wpApiSettings ) ) { 964 wpApiSettings.root = window.location.origin + '/wp-json/'; 965 } 966 967 Endpoint = Backbone.Model.extend( { 968 defaults: { 969 apiRoot: wpApiSettings.root, 970 versionString: wp.api.versionString, 971 schema: null, 972 models: {}, 973 collections: {} 974 }, 975 976 /** 977 * Initialize the Endpoint model. 978 */ 979 initialize: function() { 980 var model = this, deferred; 981 982 Backbone.Model.prototype.initialize.apply( model, arguments ); 983 984 deferred = jQuery.Deferred(); 985 model.schemaConstructed = deferred.promise(); 986 987 model.schemaModel = new wp.api.models.Schema( null, { 988 apiRoot: model.get( 'apiRoot' ), 989 versionString: model.get( 'versionString' ) 990 } ); 991 992 // When the model loads, resolve the promise. 993 model.schemaModel.once( 'change', function() { 994 model.constructFromSchema(); 995 deferred.resolve( model ); 996 } ); 997 998 if ( model.get( 'schema' ) ) { 999 1000 // Use schema supplied as model attribute. 1001 model.schemaModel.set( model.schemaModel.parse( model.get( 'schema' ) ) ); 1002 } else if ( 1003 ! _.isUndefined( sessionStorage ) && 1004 ( _.isUndefined( wpApiSettings.cacheSchema ) || wpApiSettings.cacheSchema ) && 1005 sessionStorage.getItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ) ) 1006 ) { 1007 1008 // Used a cached copy of the schema model if available. 1009 model.schemaModel.set( model.schemaModel.parse( JSON.parse( sessionStorage.getItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ) ) ) ) ); 1010 } else { 1011 model.schemaModel.fetch( { 1012 /** 1013 * When the server returns the schema model data, store the data in a sessionCache so we don't 1014 * have to retrieve it again for this session. Then, construct the models and collections based 1015 * on the schema model data. 1016 */ 1017 success: function( newSchemaModel ) { 1018 1019 // Store a copy of the schema model in the session cache if available. 1020 if ( ! _.isUndefined( sessionStorage ) && wpApiSettings.cacheSchema ) { 1021 try { 1022 sessionStorage.setItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ), JSON.stringify( newSchemaModel ) ); 1023 } catch ( error ) { 1024 1025 // Fail silently, fixes errors in safari private mode. 1026 } 1027 } 1028 }, 1029 1030 // Log the error condition. 1031 error: function( err ) { 1032 window.console.log( err ); 1033 } 1034 } ); 1035 } 1036 }, 1037 1038 constructFromSchema: function() { 1039 var routeModel = this, modelRoutes, collectionRoutes, schemaRoot, loadingObjects, 1040 1041 /** 1042 * Set up the model and collection name mapping options. As the schema is built, the 1043 * model and collection names will be adjusted if they are found in the mapping object. 1044 * 1045 * Localizing a variable wpApiSettings.mapping will over-ride the default mapping options. 1046 * 1047 */ 1048 mapping = wpApiSettings.mapping || { 1049 models: { 1050 'Categories': 'Category', 1051 'Comments': 'Comment', 1052 'Pages': 'Page', 1053 'PagesMeta': 'PageMeta', 1054 'PagesRevisions': 'PageRevision', 1055 'Posts': 'Post', 1056 'PostsCategories': 'PostCategory', 1057 'PostsRevisions': 'PostRevision', 1058 'PostsTags': 'PostTag', 1059 'Schema': 'Schema', 1060 'Statuses': 'Status', 1061 'Tags': 'Tag', 1062 'Taxonomies': 'Taxonomy', 1063 'Types': 'Type', 1064 'Users': 'User' 1065 }, 1066 collections: { 1067 'PagesMeta': 'PageMeta', 1068 'PagesRevisions': 'PageRevisions', 1069 'PostsCategories': 'PostCategories', 1070 'PostsMeta': 'PostMeta', 1071 'PostsRevisions': 'PostRevisions', 1072 'PostsTags': 'PostTags' 1073 } 1074 }; 1075 1076 /** 1077 * Iterate thru the routes, picking up models and collections to build. Builds two arrays, 1078 * one for models and one for collections. 1079 */ 1080 modelRoutes = []; 1081 collectionRoutes = []; 1082 schemaRoot = routeModel.get( 'apiRoot' ).replace( wp.api.utils.getRootUrl(), '' ); 1083 loadingObjects = {}; 1084 1085 /** 1086 * Tracking objects for models and collections. 1087 */ 1088 loadingObjects.models = routeModel.get( 'models' ); 1089 loadingObjects.collections = routeModel.get( 'collections' ); 1090 1091 _.each( routeModel.schemaModel.get( 'routes' ), function( route, index ) { 1092 1093 // Skip the schema root if included in the schema. 1094 if ( index !== routeModel.get( ' versionString' ) && 1095 index !== schemaRoot && 1096 index !== ( '/' + routeModel.get( 'versionString' ).slice( 0, -1 ) ) 1097 ) { 1098 1099 // Single items end with a regex (or the special case 'me'). 1100 if ( /(?:.*[+)]|\/me)$/.test( index ) ) { 1101 modelRoutes.push( { index: index, route: route } ); 1102 } else { 1103 1104 // Collections end in a name. 1105 collectionRoutes.push( { index: index, route: route } ); 1106 } 1107 } 1108 } ); 1109 1110 /** 1111 * Construct the models. 1112 * 1113 * Base the class name on the route endpoint. 1114 */ 1115 _.each( modelRoutes, function( modelRoute ) { 1116 1117 // Extract the name and any parent from the route. 1118 var modelClassName, 1119 routeName = wp.api.utils.extractRoutePart( modelRoute.index, 2 ), 1120 parentName = wp.api.utils.extractRoutePart( modelRoute.index, 4 ), 1121 routeEnd = wp.api.utils.extractRoutePart( modelRoute.index, 1 ); 1122 1123 // Handle the special case of the 'me' route. 1124 if ( 'me' === routeEnd ) { 1125 routeName = 'me'; 1126 } 1127 1128 // If the model has a parent in its route, add that to its class name. 1129 if ( '' !== parentName && parentName !== routeName ) { 1130 modelClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName ); 1131 modelClassName = mapping.models[ modelClassName ] || modelClassName; 1132 loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { 1133 1134 // Return a constructed url based on the parent and id. 1135 url: function() { 1136 var url = routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + 1137 parentName + '/' + 1138 ( ( _.isUndefined( this.get( 'parent' ) ) || 0 === this.get( 'parent' ) ) ? 1139 this.get( 'parent_post' ) : 1140 this.get( 'parent' ) ) + '/' + 1141 routeName; 1142 if ( ! _.isUndefined( this.get( 'id' ) ) ) { 1143 url += '/' + this.get( 'id' ); 1144 } 1145 return url; 1146 }, 1147 1148 // Include a reference to the original route object. 1149 route: modelRoute, 1150 1151 // Include a reference to the original class name. 1152 name: modelClassName, 1153 1154 // Include the array of route methods for easy reference. 1155 methods: modelRoute.route.methods, 1156 1157 initialize: function() { 1158 1159 /** 1160 * Posts and pages support trashing, other types don't support a trash 1161 * and require that you pass ?force=true to actually delete them. 1162 * 1163 * @todo we should be getting trashability from the Schema, not hard coding types here. 1164 */ 1165 if ( 1166 'Posts' !== this.name && 1167 'Pages' !== this.name && 1168 _.includes( this.methods, 'DELETE' ) 1169 ) { 1170 this.requireForceForDelete = true; 1171 } 1172 } 1173 } ); 1174 } else { 1175 1176 // This is a model without a parent in its route 1177 modelClassName = wp.api.utils.capitalize( routeName ); 1178 modelClassName = mapping.models[ modelClassName ] || modelClassName; 1179 loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { 1180 1181 // Function that returns a constructed url based on the id. 1182 url: function() { 1183 var url = routeModel.get( 'apiRoot' ) + 1184 routeModel.get( 'versionString' ) + 1185 ( ( 'me' === routeName ) ? 'users/me' : routeName ); 1186 1187 if ( ! _.isUndefined( this.get( 'id' ) ) ) { 1188 url += '/' + this.get( 'id' ); 1189 } 1190 return url; 1191 }, 1192 1193 // Include a reference to the original route object. 1194 route: modelRoute, 1195 1196 // Include a reference to the original class name. 1197 name: modelClassName, 1198 1199 // Include the array of route methods for easy reference. 1200 methods: modelRoute.route.methods 1201 } ); 1202 } 1203 1204 // Add defaults to the new model, pulled form the endpoint. 1205 wp.api.utils.decorateFromRoute( modelRoute.route.endpoints, loadingObjects.models[ modelClassName ] ); 1206 1207 } ); 1208 1209 /** 1210 * Construct the collections. 1211 * 1212 * Base the class name on the route endpoint. 1213 */ 1214 _.each( collectionRoutes, function( collectionRoute ) { 1215 1216 // Extract the name and any parent from the route. 1217 var collectionClassName, modelClassName, 1218 routeName = collectionRoute.index.slice( collectionRoute.index.lastIndexOf( '/' ) + 1 ), 1219 parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 3 ); 1220 1221 // If the collection has a parent in its route, add that to its class name. 1222 if ( '' !== parentName && parentName !== routeName ) { 1223 1224 collectionClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName ); 1225 modelClassName = mapping.models[ collectionClassName ] || collectionClassName; 1226 collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName; 1227 loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( { 1228 1229 // Function that returns a constructed url passed on the parent. 1230 url: function() { 1231 return routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + 1232 parentName + '/' + this.parent + '/' + 1233 routeName; 1234 }, 1235 1236 // Specify the model that this collection contains. 1237 model: loadingObjects.models[ modelClassName ], 1238 1239 // Include a reference to the original class name. 1240 name: collectionClassName, 1241 1242 // Include a reference to the original route object. 1243 route: collectionRoute, 1244 1245 // Include the array of route methods for easy reference. 1246 methods: collectionRoute.route.methods 1247 } ); 1248 } else { 1249 1250 // This is a collection without a parent in its route. 1251 collectionClassName = wp.api.utils.capitalize( routeName ); 1252 modelClassName = mapping.models[ collectionClassName ] || collectionClassName; 1253 collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName; 1254 loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( { 1255 1256 // For the url of a root level collection, use a string. 1257 url: routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + routeName, 1258 1259 // Specify the model that this collection contains. 1260 model: loadingObjects.models[ modelClassName ], 1261 1262 // Include a reference to the original class name. 1263 name: collectionClassName, 1264 1265 // Include a reference to the original route object. 1266 route: collectionRoute, 1267 1268 // Include the array of route methods for easy reference. 1269 methods: collectionRoute.route.methods 1270 } ); 1271 } 1272 1273 // Add defaults to the new model, pulled form the endpoint. 1274 wp.api.utils.decorateFromRoute( collectionRoute.route.endpoints, loadingObjects.collections[ collectionClassName ] ); 1275 } ); 1276 1277 // Add mixins and helpers for each of the models. 1278 _.each( loadingObjects.models, function( model, index ) { 1279 loadingObjects.models[ index ] = wp.api.utils.addMixinsAndHelpers( model, index, loadingObjects ); 1280 } ); 1281 1282 } 1283 1284 } ); 1285 1286 wp.api.endpoints = new Backbone.Collection( { 1287 model: Endpoint 1288 } ); 1289 1290 /** 1291 * Initialize the wp-api, optionally passing the API root. 1292 * 1293 * @param {object} [args] 1294 * @param {string} [args.apiRoot] The api root. Optional, defaults to wpApiSettings.root. 1295 * @param {string} [args.versionString] The version string. Optional, defaults to wpApiSettings.root. 1296 * @param {object} [args.schema] The schema. Optional, will be fetched from API if not provided. 1297 */ 1298 wp.api.init = function( args ) { 1299 var endpoint, attributes = {}, deferred, promise; 1300 1301 args = args || {}; 1302 attributes.apiRoot = args.apiRoot || wpApiSettings.root; 1303 attributes.versionString = args.versionString || wpApiSettings.versionString; 1304 attributes.schema = args.schema || null; 1305 if ( ! attributes.schema && attributes.apiRoot === wpApiSettings.root && attributes.versionString === wpApiSettings.versionString ) { 1306 attributes.schema = wpApiSettings.schema; 1307 } 1308 1309 if ( ! initializedDeferreds[ attributes.apiRoot + attributes.versionString ] ) { 1310 endpoint = wp.api.endpoints.findWhere( { apiRoot: attributes.apiRoot, versionString: attributes.versionString } ); 1311 if ( ! endpoint ) { 1312 endpoint = new Endpoint( attributes ); 1313 wp.api.endpoints.add( endpoint ); 1314 } 1315 deferred = jQuery.Deferred(); 1316 promise = deferred.promise(); 1317 1318 endpoint.schemaConstructed.done( function( endpoint ) { 1319 1320 // Map the default endpoints, extending any already present items (including Schema model). 1321 wp.api.models = _.extend( endpoint.get( 'models' ), wp.api.models ); 1322 wp.api.collections = _.extend( endpoint.get( 'collections' ), wp.api.collections ); 1323 deferred.resolveWith( wp.api, [ endpoint ] ); 1324 } ); 1325 initializedDeferreds[ attributes.apiRoot + attributes.versionString ] = promise; 1326 } 1327 return initializedDeferreds[ attributes.apiRoot + attributes.versionString ]; 1328 }; 1329 1330 /** 1331 * Construct the default endpoints and add to an endpoints collection. 1332 */ 1333 1334 // The wp.api.init function returns a promise that will resolve with the endpoint once it is ready. 1335 wp.api.loadPromise = wp.api.init(); 1336 1337 } )(); -
src/wp-includes/option.php
1708 1708 } 1709 1709 1710 1710 /** 1711 * Register default settings available in WordPress. 1712 * 1713 * The settings registered here are primarily useful for the REST API, so this 1714 * does not encompass all settings available in WordPress. 1715 * 1716 * @since 4.7.0 1717 */ 1718 function register_initial_settings() { 1719 register_setting( 'general', 'blogname', array( 1720 'show_in_rest' => array( 1721 'name' => 'title', 1722 ), 1723 'type' => 'string', 1724 'description' => __( 'Site title.' ), 1725 ) ); 1726 1727 register_setting( 'general', 'blogdescription', array( 1728 'show_in_rest' => array( 1729 'name' => 'description', 1730 ), 1731 'type' => 'string', 1732 'description' => __( 'Site description.' ), 1733 ) ); 1734 1735 register_setting( 'general', 'siteurl', array( 1736 'show_in_rest' => array( 1737 'name' => 'url', 1738 'schema' => array( 1739 'format' => 'uri', 1740 ), 1741 ), 1742 'type' => 'string', 1743 'description' => __( 'Site URL.' ), 1744 ) ); 1745 1746 register_setting( 'general', 'admin_email', array( 1747 'show_in_rest' => array( 1748 'name' => 'email', 1749 'schema' => array( 1750 'format' => 'email', 1751 ), 1752 ), 1753 'type' => 'string', 1754 'description' => __( 'This address is used for admin purposes. If you change this we will send you an email at your new address to confirm it. The new address will not become active until confirmed.' ), 1755 ) ); 1756 1757 register_setting( 'general', 'timezone_string', array( 1758 'show_in_rest' => array( 1759 'name' => 'timezone', 1760 ), 1761 'type' => 'string', 1762 'description' => __( 'A city in the same timezone as you.' ), 1763 ) ); 1764 1765 register_setting( 'general', 'date_format', array( 1766 'show_in_rest' => true, 1767 'type' => 'string', 1768 'description' => __( 'A date format for all date strings.' ), 1769 ) ); 1770 1771 register_setting( 'general', 'time_format', array( 1772 'show_in_rest' => true, 1773 'type' => 'string', 1774 'description' => __( 'A time format for all time strings.' ), 1775 ) ); 1776 1777 register_setting( 'general', 'start_of_week', array( 1778 'show_in_rest' => true, 1779 'type' => 'number', 1780 'description' => __( 'A day number of the week that the week should start on.' ), 1781 ) ); 1782 1783 register_setting( 'general', 'WPLANG', array( 1784 'show_in_rest' => array( 1785 'name' => 'language', 1786 ), 1787 'type' => 'string', 1788 'description' => __( 'WordPress locale code.' ), 1789 'default' => 'en_US', 1790 ) ); 1791 1792 register_setting( 'writing', 'use_smilies', array( 1793 'show_in_rest' => true, 1794 'type' => 'boolean', 1795 'description' => __( 'Convert emoticons like :-) and :-P to graphics on display.' ), 1796 'default' => true, 1797 ) ); 1798 1799 register_setting( 'writing', 'default_category', array( 1800 'show_in_rest' => true, 1801 'type' => 'number', 1802 'description' => __( 'Default category.' ), 1803 ) ); 1804 1805 register_setting( 'writing', 'default_post_format', array( 1806 'show_in_rest' => true, 1807 'type' => 'string', 1808 'description' => __( 'Default post format.' ), 1809 ) ); 1810 1811 register_setting( 'reading', 'posts_per_page', array( 1812 'show_in_rest' => true, 1813 'type' => 'number', 1814 'description' => __( 'Blog pages show at most.' ), 1815 'default' => 10, 1816 ) ); 1817 } 1818 1819 /** 1711 1820 * Register a setting and its data. 1712 1821 * 1713 1822 * @since 2.7.0 -
src/wp-includes/post.php
33 33 'query_var' => false, 34 34 'delete_with_user' => true, 35 35 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ), 36 'show_in_rest' => true, 37 'rest_base' => 'posts', 38 'rest_controller_class' => 'WP_REST_Posts_Controller', 36 39 ) ); 37 40 38 41 register_post_type( 'page', array( … … 51 54 'query_var' => false, 52 55 'delete_with_user' => true, 53 56 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ), 57 'show_in_rest' => true, 58 'rest_base' => 'pages', 59 'rest_controller_class' => 'WP_REST_Posts_Controller', 54 60 ) ); 55 61 56 62 register_post_type( 'attachment', array( … … 76 82 'show_in_nav_menus' => false, 77 83 'delete_with_user' => true, 78 84 'supports' => array( 'title', 'author', 'comments' ), 85 'show_in_rest' => true, 86 'rest_base' => 'media', 87 'rest_controller_class' => 'WP_REST_Attachments_Controller', 79 88 ) ); 80 89 add_post_type_support( 'attachment:audio', 'thumbnail' ); 81 90 add_post_type_support( 'attachment:video', 'thumbnail' ); -
src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
1 <?php 2 3 class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { 4 5 /** 6 * Determine the allowed query_vars for a get_items() response and 7 * prepare for WP_Query. 8 * 9 * @param array $prepared_args Optional. Array of prepared arguments. 10 * @param WP_REST_Request $request Optional. Request to prepare items for. 11 * @return array Array of query arguments. 12 */ 13 protected function prepare_items_query( $prepared_args = array(), $request = null ) { 14 $query_args = parent::prepare_items_query( $prepared_args, $request ); 15 if ( empty( $query_args['post_status'] ) || ! in_array( $query_args['post_status'], array( 'inherit', 'private', 'trash' ), true ) ) { 16 $query_args['post_status'] = 'inherit'; 17 } 18 $media_types = $this->get_media_types(); 19 if ( ! empty( $request['media_type'] ) && isset( $media_types[ $request['media_type'] ] ) ) { 20 $query_args['post_mime_type'] = $media_types[ $request['media_type'] ]; 21 } 22 if ( ! empty( $request['mime_type'] ) ) { 23 $parts = explode( '/', $request['mime_type'] ); 24 if ( isset( $media_types[ $parts[0] ] ) && in_array( $request['mime_type'], $media_types[ $parts[0] ], true ) ) { 25 $query_args['post_mime_type'] = $request['mime_type']; 26 } 27 } 28 return $query_args; 29 } 30 31 /** 32 * Check if a given request has access to create an attachment. 33 * 34 * @param WP_REST_Request $request Full details about the request. 35 * @return WP_Error|true Boolean true if the attachment may be created, or a WP_Error if not. 36 */ 37 public function create_item_permissions_check( $request ) { 38 $ret = parent::create_item_permissions_check( $request ); 39 if ( ! $ret || is_wp_error( $ret ) ) { 40 return $ret; 41 } 42 43 if ( ! current_user_can( 'upload_files' ) ) { 44 return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) ); 45 } 46 47 // Attaching media to a post requires ability to edit said post. 48 if ( ! empty( $request['post'] ) ) { 49 $parent = $this->get_post( (int) $request['post'] ); 50 $post_parent_type = get_post_type_object( $parent->post_type ); 51 if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) { 52 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this resource.' ), array( 'status' => rest_authorization_required_code() ) ); 53 } 54 } 55 56 return true; 57 } 58 59 /** 60 * Create a single attachment. 61 * 62 * @param WP_REST_Request $request Full details about the request. 63 * @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure. 64 */ 65 public function create_item( $request ) { 66 67 if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) { 68 return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) ); 69 } 70 71 // Get the file via $_FILES or raw data 72 $files = $request->get_file_params(); 73 $headers = $request->get_headers(); 74 if ( ! empty( $files ) ) { 75 $file = $this->upload_from_file( $files, $headers ); 76 } else { 77 $file = $this->upload_from_data( $request->get_body(), $headers ); 78 } 79 80 if ( is_wp_error( $file ) ) { 81 return $file; 82 } 83 84 $name = basename( $file['file'] ); 85 $name_parts = pathinfo( $name ); 86 $name = trim( substr( $name, 0, -(1 + strlen( $name_parts['extension'] ) ) ) ); 87 88 $url = $file['url']; 89 $type = $file['type']; 90 $file = $file['file']; 91 92 // use image exif/iptc data for title and caption defaults if possible 93 // @codingStandardsIgnoreStart 94 $image_meta = @wp_read_image_metadata( $file ); 95 // @codingStandardsIgnoreEnd 96 if ( ! empty( $image_meta ) ) { 97 if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) { 98 $request['title'] = $image_meta['title']; 99 } 100 101 if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) { 102 $request['caption'] = $image_meta['caption']; 103 } 104 } 105 106 $attachment = $this->prepare_item_for_database( $request ); 107 $attachment->file = $file; 108 $attachment->post_mime_type = $type; 109 $attachment->guid = $url; 110 111 if ( empty( $attachment->post_title ) ) { 112 $attachment->post_title = preg_replace( '/\.[^.]+$/', '', basename( $file ) ); 113 } 114 115 $id = wp_insert_post( $attachment, true ); 116 if ( is_wp_error( $id ) ) { 117 if ( 'db_update_error' === $id->get_error_code() ) { 118 $id->add_data( array( 'status' => 500 ) ); 119 } else { 120 $id->add_data( array( 'status' => 400 ) ); 121 } 122 return $id; 123 } 124 $attachment = $this->get_post( $id ); 125 126 // Include admin functions to get access to wp_generate_attachment_metadata(). 127 require_once ABSPATH . 'wp-admin/includes/admin.php'; 128 129 wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) ); 130 131 if ( isset( $request['alt_text'] ) ) { 132 update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) ); 133 } 134 135 $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); 136 if ( is_wp_error( $fields_update ) ) { 137 return $fields_update; 138 } 139 140 $request->set_param( 'context', 'edit' ); 141 $response = $this->prepare_item_for_response( $attachment, $request ); 142 $response = rest_ensure_response( $response ); 143 $response->set_status( 201 ); 144 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) ); 145 146 /** 147 * Fires after a single attachment is created or updated via the REST API. 148 * 149 * @param object $attachment Inserted attachment. 150 * @param WP_REST_Request $request The request sent to the API. 151 * @param boolean $creating True when creating an attachment, false when updating. 152 */ 153 do_action( 'rest_insert_attachment', $attachment, $request, true ); 154 155 return $response; 156 157 } 158 159 /** 160 * Update a single post. 161 * 162 * @param WP_REST_Request $request Full details about the request. 163 * @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure. 164 */ 165 public function update_item( $request ) { 166 if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) { 167 return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) ); 168 } 169 $response = parent::update_item( $request ); 170 if ( is_wp_error( $response ) ) { 171 return $response; 172 } 173 174 $response = rest_ensure_response( $response ); 175 $data = $response->get_data(); 176 177 if ( isset( $request['alt_text'] ) ) { 178 update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] ); 179 } 180 181 $attachment = $this->get_post( $request['id'] ); 182 183 $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); 184 if ( is_wp_error( $fields_update ) ) { 185 return $fields_update; 186 } 187 188 $request->set_param( 'context', 'edit' ); 189 $response = $this->prepare_item_for_response( $attachment, $request ); 190 $response = rest_ensure_response( $response ); 191 192 /* This action is documented in lib/endpoints/class-wp-rest-attachments-controller.php */ 193 do_action( 'rest_insert_attachment', $data, $request, false ); 194 195 return $response; 196 } 197 198 /** 199 * Prepare a single attachment for create or update. 200 * 201 * @param WP_REST_Request $request Request object. 202 * @return WP_Error|stdClass $prepared_attachment Post object. 203 */ 204 protected function prepare_item_for_database( $request ) { 205 $prepared_attachment = parent::prepare_item_for_database( $request ); 206 207 if ( isset( $request['caption'] ) ) { 208 $prepared_attachment->post_excerpt = $request['caption']; 209 } 210 211 if ( isset( $request['description'] ) ) { 212 $prepared_attachment->post_content = $request['description']; 213 } 214 215 if ( isset( $request['post'] ) ) { 216 $prepared_attachment->post_parent = (int) $request['post']; 217 } 218 219 return $prepared_attachment; 220 } 221 222 /** 223 * Prepare a single attachment output for response. 224 * 225 * @param WP_Post $post Post object. 226 * @param WP_REST_Request $request Request object. 227 * @return WP_REST_Response Response object. 228 */ 229 public function prepare_item_for_response( $post, $request ) { 230 $response = parent::prepare_item_for_response( $post, $request ); 231 $data = $response->get_data(); 232 233 $data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ); 234 $data['caption'] = $post->post_excerpt; 235 $data['description'] = $post->post_content; 236 $data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file'; 237 $data['mime_type'] = $post->post_mime_type; 238 $data['media_details'] = wp_get_attachment_metadata( $post->ID ); 239 $data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null; 240 $data['source_url'] = wp_get_attachment_url( $post->ID ); 241 242 // Ensure empty details is an empty object. 243 if ( empty( $data['media_details'] ) ) { 244 $data['media_details'] = new stdClass; 245 } elseif ( ! empty( $data['media_details']['sizes'] ) ) { 246 247 foreach ( $data['media_details']['sizes'] as $size => &$size_data ) { 248 249 if ( isset( $size_data['mime-type'] ) ) { 250 $size_data['mime_type'] = $size_data['mime-type']; 251 unset( $size_data['mime-type'] ); 252 } 253 254 // Use the same method image_downsize() does. 255 $image_src = wp_get_attachment_image_src( $post->ID, $size ); 256 if ( ! $image_src ) { 257 continue; 258 } 259 260 $size_data['source_url'] = $image_src[0]; 261 } 262 263 $full_src = wp_get_attachment_image_src( $post->ID, 'full' ); 264 if ( ! empty( $full_src ) ) { 265 $data['media_details']['sizes']['full'] = array( 266 'file' => wp_basename( $full_src[0] ), 267 'width' => $full_src[1], 268 'height' => $full_src[2], 269 'mime_type' => $post->post_mime_type, 270 'source_url' => $full_src[0], 271 ); 272 } 273 } else { 274 $data['media_details']['sizes'] = new stdClass; 275 } 276 277 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 278 279 $data = $this->filter_response_by_context( $data, $context ); 280 281 // Wrap the data in a response object. 282 $response = rest_ensure_response( $data ); 283 284 $response->add_links( $this->prepare_links( $post ) ); 285 286 /** 287 * Filter an attachment returned from the API. 288 * 289 * Allows modification of the attachment right before it is returned. 290 * 291 * @param WP_REST_Response $response The response object. 292 * @param WP_Post $post The original attachment post. 293 * @param WP_REST_Request $request Request used to generate the response. 294 */ 295 return apply_filters( 'rest_prepare_attachment', $response, $post, $request ); 296 } 297 298 /** 299 * Get the Attachment's schema, conforming to JSON Schema. 300 * 301 * @return array Item schema as an array. 302 */ 303 public function get_item_schema() { 304 305 $schema = parent::get_item_schema(); 306 307 $schema['properties']['alt_text'] = array( 308 'description' => __( 'Alternative text to display when resource is not displayed.' ), 309 'type' => 'string', 310 'context' => array( 'view', 'edit', 'embed' ), 311 'arg_options' => array( 312 'sanitize_callback' => 'sanitize_text_field', 313 ), 314 ); 315 $schema['properties']['caption'] = array( 316 'description' => __( 'The caption for the resource.' ), 317 'type' => 'string', 318 'context' => array( 'view', 'edit' ), 319 'arg_options' => array( 320 'sanitize_callback' => 'wp_filter_post_kses', 321 ), 322 ); 323 $schema['properties']['description'] = array( 324 'description' => __( 'The description for the resource.' ), 325 'type' => 'string', 326 'context' => array( 'view', 'edit' ), 327 'arg_options' => array( 328 'sanitize_callback' => 'wp_filter_post_kses', 329 ), 330 ); 331 $schema['properties']['media_type'] = array( 332 'description' => __( 'Type of resource.' ), 333 'type' => 'string', 334 'enum' => array( 'image', 'file' ), 335 'context' => array( 'view', 'edit', 'embed' ), 336 'readonly' => true, 337 ); 338 $schema['properties']['mime_type'] = array( 339 'description' => __( 'MIME type of resource.' ), 340 'type' => 'string', 341 'context' => array( 'view', 'edit', 'embed' ), 342 'readonly' => true, 343 ); 344 $schema['properties']['media_details'] = array( 345 'description' => __( 'Details about the resource file, specific to its type.' ), 346 'type' => 'object', 347 'context' => array( 'view', 'edit', 'embed' ), 348 'readonly' => true, 349 ); 350 $schema['properties']['post'] = array( 351 'description' => __( 'The id for the associated post of the resource.' ), 352 'type' => 'integer', 353 'context' => array( 'view', 'edit' ), 354 ); 355 $schema['properties']['source_url'] = array( 356 'description' => __( 'URL to the original resource file.' ), 357 'type' => 'string', 358 'format' => 'uri', 359 'context' => array( 'view', 'edit', 'embed' ), 360 'readonly' => true, 361 ); 362 return $schema; 363 } 364 365 /** 366 * Handle an upload via raw POST data. 367 * 368 * @param array $data Supplied file data. 369 * @param array $headers HTTP headers from the request. 370 * @return array|WP_Error Data from {@see wp_handle_sideload()}. 371 */ 372 protected function upload_from_data( $data, $headers ) { 373 if ( empty( $data ) ) { 374 return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) ); 375 } 376 377 if ( empty( $headers['content_type'] ) ) { 378 return new WP_Error( 'rest_upload_no_content_type', __( 'No Content-Type supplied.' ), array( 'status' => 400 ) ); 379 } 380 381 if ( empty( $headers['content_disposition'] ) ) { 382 return new WP_Error( 'rest_upload_no_content_disposition', __( 'No Content-Disposition supplied.' ), array( 'status' => 400 ) ); 383 } 384 385 $filename = self::get_filename_from_disposition( $headers['content_disposition'] ); 386 387 if ( empty( $filename ) ) { 388 return new WP_Error( 'rest_upload_invalid_disposition', __( 'Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as `attachment; filename="image.png"` or similar.' ), array( 'status' => 400 ) ); 389 } 390 391 if ( ! empty( $headers['content_md5'] ) ) { 392 $content_md5 = array_shift( $headers['content_md5'] ); 393 $expected = trim( $content_md5 ); 394 $actual = md5( $data ); 395 396 if ( $expected !== $actual ) { 397 return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) ); 398 } 399 } 400 401 // Get the content-type. 402 $type = array_shift( $headers['content_type'] ); 403 404 /** Include admin functions to get access to wp_tempnam() and wp_handle_sideload() */ 405 require_once ABSPATH . 'wp-admin/includes/admin.php'; 406 407 // Save the file. 408 $tmpfname = wp_tempnam( $filename ); 409 410 $fp = fopen( $tmpfname, 'w+' ); 411 412 if ( ! $fp ) { 413 return new WP_Error( 'rest_upload_file_error', __( 'Could not open file handle.' ), array( 'status' => 500 ) ); 414 } 415 416 fwrite( $fp, $data ); 417 fclose( $fp ); 418 419 // Now, sideload it in. 420 $file_data = array( 421 'error' => null, 422 'tmp_name' => $tmpfname, 423 'name' => $filename, 424 'type' => $type, 425 ); 426 $overrides = array( 427 'test_form' => false, 428 ); 429 $sideloaded = wp_handle_sideload( $file_data, $overrides ); 430 431 if ( isset( $sideloaded['error'] ) ) { 432 // @codingStandardsIgnoreStart 433 @unlink( $tmpfname ); 434 // @codingStandardsIgnoreEnd 435 return new WP_Error( 'rest_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) ); 436 } 437 438 return $sideloaded; 439 } 440 441 /** 442 * Parse filename from a Content-Disposition header value. 443 * 444 * As per RFC6266: 445 * 446 * content-disposition = "Content-Disposition" ":" 447 * disposition-type *( ";" disposition-parm ) 448 * 449 * disposition-type = "inline" | "attachment" | disp-ext-type 450 * ; case-insensitive 451 * disp-ext-type = token 452 * 453 * disposition-parm = filename-parm | disp-ext-parm 454 * 455 * filename-parm = "filename" "=" value 456 * | "filename*" "=" ext-value 457 * 458 * disp-ext-parm = token "=" value 459 * | ext-token "=" ext-value 460 * ext-token = <the characters in token, followed by "*"> 461 * 462 * @see http://tools.ietf.org/html/rfc2388 463 * @see http://tools.ietf.org/html/rfc6266 464 * 465 * @param string[] $disposition_header List of Content-Disposition header values. 466 * @return string|null Filename if available, or null if not found. 467 */ 468 public static function get_filename_from_disposition( $disposition_header ) { 469 // Get the filename. 470 $filename = null; 471 472 foreach ( $disposition_header as $value ) { 473 $value = trim( $value ); 474 475 if ( strpos( $value, ';' ) === false ) { 476 continue; 477 } 478 479 list( $type, $attr_parts ) = explode( ';', $value, 2 ); 480 $attr_parts = explode( ';', $attr_parts ); 481 $attributes = array(); 482 foreach ( $attr_parts as $part ) { 483 if ( strpos( $part, '=' ) === false ) { 484 continue; 485 } 486 487 list( $key, $value ) = explode( '=', $part, 2 ); 488 $attributes[ trim( $key ) ] = trim( $value ); 489 } 490 491 if ( empty( $attributes['filename'] ) ) { 492 continue; 493 } 494 495 $filename = trim( $attributes['filename'] ); 496 497 // Unquote quoted filename, but after trimming. 498 if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) { 499 $filename = substr( $filename, 1, -1 ); 500 } 501 } 502 503 return $filename; 504 } 505 506 /** 507 * Get the query params for collections of attachments. 508 * 509 * @return array Query parameters for the attachment collection as an array. 510 */ 511 public function get_collection_params() { 512 $params = parent::get_collection_params(); 513 $params['status']['default'] = 'inherit'; 514 $params['status']['enum'] = array( 'inherit', 'private', 'trash' ); 515 $media_types = $this->get_media_types(); 516 $params['media_type'] = array( 517 'default' => null, 518 'description' => __( 'Limit result set to attachments of a particular media type.' ), 519 'type' => 'string', 520 'enum' => array_keys( $media_types ), 521 'validate_callback' => 'rest_validate_request_arg', 522 ); 523 $params['mime_type'] = array( 524 'default' => null, 525 'description' => __( 'Limit result set to attachments of a particular MIME type.' ), 526 'type' => 'string', 527 ); 528 return $params; 529 } 530 531 /** 532 * Validate whether the user can query private statuses 533 * 534 * @param mixed $value Status value. 535 * @param WP_REST_Request $request Request object. 536 * @param string $parameter Additional parameter to pass to validation. 537 * @return WP_Error|boolean Boolean true if the user may query, WP_Error if not. 538 */ 539 public function validate_user_can_query_private_statuses( $value, $request, $parameter ) { 540 if ( 'inherit' === $value ) { 541 return true; 542 } 543 return parent::validate_user_can_query_private_statuses( $value, $request, $parameter ); 544 } 545 546 /** 547 * Handle an upload via multipart/form-data ($_FILES). 548 * 549 * @param array $files Data from $_FILES. 550 * @param array $headers HTTP headers from the request. 551 * @return array|WP_Error Data from {@see wp_handle_upload()}. 552 */ 553 protected function upload_from_file( $files, $headers ) { 554 if ( empty( $files ) ) { 555 return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) ); 556 } 557 558 // Verify hash, if given. 559 if ( ! empty( $headers['content_md5'] ) ) { 560 $content_md5 = array_shift( $headers['content_md5'] ); 561 $expected = trim( $content_md5 ); 562 $actual = md5_file( $files['file']['tmp_name'] ); 563 if ( $expected !== $actual ) { 564 return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) ); 565 } 566 } 567 568 // Pass off to WP to handle the actual upload. 569 $overrides = array( 570 'test_form' => false, 571 ); 572 // Bypasses is_uploaded_file() when running unit tests. 573 if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) { 574 $overrides['action'] = 'wp_handle_mock_upload'; 575 } 576 577 // Include admin functions to get access to wp_handle_upload(). 578 require_once ABSPATH . 'wp-admin/includes/admin.php'; 579 $file = wp_handle_upload( $files['file'], $overrides ); 580 581 if ( isset( $file['error'] ) ) { 582 return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) ); 583 } 584 585 return $file; 586 } 587 588 /** 589 * Get the supported media types. 590 * 591 * Media types are considered the MIME type category. 592 * 593 * @return array 594 */ 595 protected function get_media_types() { 596 $media_types = array(); 597 foreach ( get_allowed_mime_types() as $mime_type ) { 598 $parts = explode( '/', $mime_type ); 599 if ( ! isset( $media_types[ $parts[0] ] ) ) { 600 $media_types[ $parts[0] ] = array(); 601 } 602 $media_types[ $parts[0] ][] = $mime_type; 603 } 604 return $media_types; 605 } 606 607 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 /** 4 * Access comments 5 */ 6 class WP_REST_Comments_Controller extends WP_REST_Controller { 7 8 /** 9 * Instance of a comment meta fields object. 10 * 11 * @access protected 12 * @var WP_REST_Comment_Meta_Fields 13 */ 14 protected $meta; 15 16 public function __construct() { 17 $this->namespace = 'wp/v2'; 18 $this->rest_base = 'comments'; 19 20 $this->meta = new WP_REST_Comment_Meta_Fields(); 21 } 22 23 /** 24 * Register the routes for the objects of the controller. 25 */ 26 public function register_routes() { 27 28 register_rest_route( $this->namespace, '/' . $this->rest_base, array( 29 array( 30 'methods' => WP_REST_Server::READABLE, 31 'callback' => array( $this, 'get_items' ), 32 'permission_callback' => array( $this, 'get_items_permissions_check' ), 33 'args' => $this->get_collection_params(), 34 ), 35 array( 36 'methods' => WP_REST_Server::CREATABLE, 37 'callback' => array( $this, 'create_item' ), 38 'permission_callback' => array( $this, 'create_item_permissions_check' ), 39 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 40 ), 41 'schema' => array( $this, 'get_public_item_schema' ), 42 ) ); 43 44 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 45 array( 46 'methods' => WP_REST_Server::READABLE, 47 'callback' => array( $this, 'get_item' ), 48 'permission_callback' => array( $this, 'get_item_permissions_check' ), 49 'args' => array( 50 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 51 ), 52 ), 53 array( 54 'methods' => WP_REST_Server::EDITABLE, 55 'callback' => array( $this, 'update_item' ), 56 'permission_callback' => array( $this, 'update_item_permissions_check' ), 57 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 58 ), 59 array( 60 'methods' => WP_REST_Server::DELETABLE, 61 'callback' => array( $this, 'delete_item' ), 62 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 63 'args' => array( 64 'force' => array( 65 'default' => false, 66 'description' => __( 'Whether to bypass trash and force deletion.' ), 67 ), 68 ), 69 ), 70 'schema' => array( $this, 'get_public_item_schema' ), 71 ) ); 72 } 73 74 /** 75 * Check if a given request has access to read comments 76 * 77 * @param WP_REST_Request $request Full details about the request. 78 * @return WP_Error|boolean 79 */ 80 public function get_items_permissions_check( $request ) { 81 82 if ( ! empty( $request['post'] ) ) { 83 foreach ( (array) $request['post'] as $post_id ) { 84 $post = $this->get_post( $post_id ); 85 if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post ) ) { 86 return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); 87 } elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) { 88 return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read comments without a post.' ), array( 'status' => rest_authorization_required_code() ) ); 89 } 90 } 91 } 92 93 if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { 94 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 95 } 96 97 if ( ! current_user_can( 'edit_posts' ) ) { 98 $protected_params = array( 'author', 'author_exclude', 'karma', 'author_email', 'type', 'status' ); 99 $forbidden_params = array(); 100 foreach ( $protected_params as $param ) { 101 if ( 'status' === $param ) { 102 if ( 'approve' !== $request[ $param ] ) { 103 $forbidden_params[] = $param; 104 } 105 } elseif ( 'type' === $param ) { 106 if ( 'comment' !== $request[ $param ] ) { 107 $forbidden_params[] = $param; 108 } 109 } elseif ( ! empty( $request[ $param ] ) ) { 110 $forbidden_params[] = $param; 111 } 112 } 113 if ( ! empty( $forbidden_params ) ) { 114 return new WP_Error( 'rest_forbidden_param', sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), array( 'status' => rest_authorization_required_code() ) ); 115 } 116 } 117 118 return true; 119 } 120 121 /** 122 * Get a list of comments. 123 * 124 * @param WP_REST_Request $request Full details about the request. 125 * @return WP_Error|WP_REST_Response 126 */ 127 public function get_items( $request ) { 128 129 // Retrieve the list of registered collection query parameters. 130 $registered = $this->get_collection_params(); 131 132 // This array defines mappings between public API query parameters whose 133 // values are accepted as-passed, and their internal WP_Query parameter 134 // name equivalents (some are the same). Only values which are also 135 // present in $registered will be set. 136 $parameter_mappings = array( 137 'author' => 'author__in', 138 'author_email' => 'author_email', 139 'author_exclude' => 'author__not_in', 140 'exclude' => 'comment__not_in', 141 'include' => 'comment__in', 142 'karma' => 'karma', 143 'offset' => 'offset', 144 'order' => 'order', 145 'parent' => 'parent__in', 146 'parent_exclude' => 'parent__not_in', 147 'per_page' => 'number', 148 'post' => 'post__in', 149 'search' => 'search', 150 'status' => 'status', 151 'type' => 'type', 152 ); 153 154 $prepared_args = array(); 155 156 // For each known parameter which is both registered and present in the request, 157 // set the parameter's value on the query $prepared_args. 158 foreach ( $parameter_mappings as $api_param => $wp_param ) { 159 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { 160 $prepared_args[ $wp_param ] = $request[ $api_param ]; 161 } 162 } 163 164 // Ensure certain parameter values default to empty strings. 165 foreach ( array( 'author_email', 'karma', 'search' ) as $param ) { 166 if ( ! isset( $prepared_args[ $param ] ) ) { 167 $prepared_args[ $param ] = ''; 168 } 169 } 170 171 if ( isset( $registered['orderby'] ) ) { 172 $prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] ); 173 } 174 175 $prepared_args['no_found_rows'] = false; 176 177 $prepared_args['date_query'] = array(); 178 // Set before into date query. Date query must be specified as an array of an array. 179 if ( isset( $registered['before'], $request['before'] ) ) { 180 $prepared_args['date_query'][0]['before'] = $request['before']; 181 } 182 183 // Set after into date query. Date query must be specified as an array of an array. 184 if ( isset( $registered['after'], $request['after'] ) ) { 185 $prepared_args['date_query'][0]['after'] = $request['after']; 186 } 187 188 if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) { 189 $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 ); 190 } 191 192 /** 193 * Filter arguments, before passing to WP_Comment_Query, when querying comments via the REST API. 194 * 195 * @see https://developer.wordpress.org/reference/classes/wp_comment_query/ 196 * 197 * @param array $prepared_args Array of arguments for WP_Comment_Query. 198 * @param WP_REST_Request $request The current request. 199 */ 200 $prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request ); 201 202 $query = new WP_Comment_Query; 203 $query_result = $query->query( $prepared_args ); 204 205 $comments = array(); 206 foreach ( $query_result as $comment ) { 207 if ( ! $this->check_read_permission( $comment ) ) { 208 continue; 209 } 210 211 $data = $this->prepare_item_for_response( $comment, $request ); 212 $comments[] = $this->prepare_response_for_collection( $data ); 213 } 214 215 $total_comments = (int) $query->found_comments; 216 $max_pages = (int) $query->max_num_pages; 217 if ( $total_comments < 1 ) { 218 // Out-of-bounds, run the query again without LIMIT for total count 219 unset( $prepared_args['number'], $prepared_args['offset'] ); 220 $query = new WP_Comment_Query; 221 $prepared_args['count'] = true; 222 223 $total_comments = $query->query( $prepared_args ); 224 $max_pages = ceil( $total_comments / $request['per_page'] ); 225 } 226 227 $response = rest_ensure_response( $comments ); 228 $response->header( 'X-WP-Total', $total_comments ); 229 $response->header( 'X-WP-TotalPages', $max_pages ); 230 231 $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); 232 if ( $request['page'] > 1 ) { 233 $prev_page = $request['page'] - 1; 234 if ( $prev_page > $max_pages ) { 235 $prev_page = $max_pages; 236 } 237 $prev_link = add_query_arg( 'page', $prev_page, $base ); 238 $response->link_header( 'prev', $prev_link ); 239 } 240 if ( $max_pages > $request['page'] ) { 241 $next_page = $request['page'] + 1; 242 $next_link = add_query_arg( 'page', $next_page, $base ); 243 $response->link_header( 'next', $next_link ); 244 } 245 246 return $response; 247 } 248 249 /** 250 * Check if a given request has access to read the comment 251 * 252 * @param WP_REST_Request $request Full details about the request. 253 * @return WP_Error|boolean 254 */ 255 public function get_item_permissions_check( $request ) { 256 $id = (int) $request['id']; 257 258 $comment = get_comment( $id ); 259 260 if ( ! $comment ) { 261 return true; 262 } 263 264 if ( ! $this->check_read_permission( $comment ) ) { 265 return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read this comment.' ), array( 'status' => rest_authorization_required_code() ) ); 266 } 267 268 $post = $this->get_post( $comment->comment_post_ID ); 269 270 if ( $post && ! $this->check_read_post_permission( $post ) ) { 271 return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); 272 } 273 274 if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { 275 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this comment with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 276 } 277 278 return true; 279 } 280 281 /** 282 * Get a comment. 283 * 284 * @param WP_REST_Request $request Full details about the request. 285 * @return WP_Error|WP_REST_Response 286 */ 287 public function get_item( $request ) { 288 $id = (int) $request['id']; 289 290 $comment = get_comment( $id ); 291 if ( empty( $comment ) ) { 292 return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) ); 293 } 294 295 if ( ! empty( $comment->comment_post_ID ) ) { 296 $post = $this->get_post( $comment->comment_post_ID ); 297 if ( empty( $post ) ) { 298 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) ); 299 } 300 } 301 302 $data = $this->prepare_item_for_response( $comment, $request ); 303 $response = rest_ensure_response( $data ); 304 305 return $response; 306 } 307 308 /** 309 * Check if a given request has access to create a comment 310 * 311 * @param WP_REST_Request $request Full details about the request. 312 * @return WP_Error|boolean 313 */ 314 public function create_item_permissions_check( $request ) { 315 316 if ( ! is_user_logged_in() && get_option( 'comment_registration' ) ) { 317 return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) ); 318 } 319 320 // Limit who can set comment `author`, `karma` or `status` to anything other than the default. 321 if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) { 322 return new WP_Error( 'rest_comment_invalid_author', __( 'Comment author invalid.' ), array( 'status' => rest_authorization_required_code() ) ); 323 } 324 if ( isset( $request['karma'] ) && $request['karma'] > 0 && ! current_user_can( 'moderate_comments' ) ) { 325 return new WP_Error( 'rest_comment_invalid_karma', __( 'Sorry, you cannot set karma for comments.' ), array( 'status' => rest_authorization_required_code() ) ); 326 } 327 if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) { 328 return new WP_Error( 'rest_comment_invalid_status', __( 'Sorry, you cannot set status for comments.' ), array( 'status' => rest_authorization_required_code() ) ); 329 } 330 331 if ( empty( $request['post'] ) && ! current_user_can( 'moderate_comments' ) ) { 332 return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you cannot create this comment without a post.' ), array( 'status' => rest_authorization_required_code() ) ); 333 } 334 335 if ( ! empty( $request['post'] ) && $post = $this->get_post( (int) $request['post'] ) ) { 336 if ( 'draft' === $post->post_status ) { 337 return new WP_Error( 'rest_comment_draft_post', __( 'Sorry, you cannot create a comment on this post.' ), array( 'status' => 403 ) ); 338 } 339 340 if ( 'trash' === $post->post_status ) { 341 return new WP_Error( 'rest_comment_trash_post', __( 'Sorry, you cannot create a comment on this post.' ), array( 'status' => 403 ) ); 342 } 343 344 if ( ! $this->check_read_post_permission( $post ) ) { 345 return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); 346 } 347 348 if ( ! comments_open( $post->ID ) ) { 349 return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed on this post.' ), array( 'status' => 403 ) ); 350 } 351 } 352 353 return true; 354 } 355 356 /** 357 * Create a comment. 358 * 359 * @param WP_REST_Request $request Full details about the request. 360 * @return WP_Error|WP_REST_Response 361 */ 362 public function create_item( $request ) { 363 if ( ! empty( $request['id'] ) ) { 364 return new WP_Error( 'rest_comment_exists', __( 'Cannot create existing comment.' ), array( 'status' => 400 ) ); 365 } 366 367 $prepared_comment = $this->prepare_item_for_database( $request ); 368 if ( is_wp_error( $prepared_comment ) ) { 369 return $prepared_comment; 370 } 371 372 /** 373 * Do not allow a comment to be created with an empty string for 374 * comment_content. 375 * See `wp_handle_comment_submission()`. 376 */ 377 if ( '' === $prepared_comment['comment_content'] ) { 378 return new WP_Error( 'rest_comment_content_invalid', __( 'Comment content is invalid.' ), array( 'status' => 400 ) ); 379 } 380 381 // Setting remaining values before wp_insert_comment so we can 382 // use wp_allow_comment(). 383 if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) { 384 $prepared_comment['comment_date_gmt'] = current_time( 'mysql', true ); 385 } 386 387 // Set author data if the user's logged in 388 $missing_author = empty( $prepared_comment['user_id'] ) 389 && empty( $prepared_comment['comment_author'] ) 390 && empty( $prepared_comment['comment_author_email'] ) 391 && empty( $prepared_comment['comment_author_url'] ); 392 393 if ( is_user_logged_in() && $missing_author ) { 394 $user = wp_get_current_user(); 395 $prepared_comment['user_id'] = $user->ID; 396 $prepared_comment['comment_author'] = $user->display_name; 397 $prepared_comment['comment_author_email'] = $user->user_email; 398 $prepared_comment['comment_author_url'] = $user->user_url; 399 } 400 401 // Honor the discussion setting that requires a name and email address 402 // of the comment author. 403 if ( get_option( 'require_name_email' ) ) { 404 if ( ! isset( $prepared_comment['comment_author'] ) && ! isset( $prepared_comment['comment_author_email'] ) ) { 405 return new WP_Error( 'rest_comment_author_data_required', __( 'Creating a comment requires valid author name and email values.' ), array( 'status' => 400 ) ); 406 } 407 if ( ! isset( $prepared_comment['comment_author'] ) ) { 408 return new WP_Error( 'rest_comment_author_required', __( 'Creating a comment requires a valid author name.' ), array( 'status' => 400 ) ); 409 } 410 if ( ! isset( $prepared_comment['comment_author_email'] ) ) { 411 return new WP_Error( 'rest_comment_author_email_required', __( 'Creating a comment requires a valid author email.' ), array( 'status' => 400 ) ); 412 } 413 } 414 415 if ( ! isset( $prepared_comment['comment_author_email'] ) ) { 416 $prepared_comment['comment_author_email'] = ''; 417 } 418 if ( ! isset( $prepared_comment['comment_author_url'] ) ) { 419 $prepared_comment['comment_author_url'] = ''; 420 } 421 422 $prepared_comment['comment_agent'] = ''; 423 $prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment, true ); 424 425 if ( is_wp_error( $prepared_comment['comment_approved'] ) ) { 426 $error_code = $prepared_comment['comment_approved']->get_error_code(); 427 $error_message = $prepared_comment['comment_approved']->get_error_message(); 428 429 if ( 'comment_duplicate' === $error_code ) { 430 return new WP_Error( $error_code, $error_message, array( 'status' => 409 ) ); 431 } 432 433 if ( 'comment_flood' === $error_code ) { 434 return new WP_Error( $error_code, $error_message, array( 'status' => 400 ) ); 435 } 436 437 return $prepared_comment['comment_approved']; 438 } 439 440 /** 441 * Filter a comment before it is inserted via the REST API. 442 * 443 * Allows modification of the comment right before it is inserted via `wp_insert_comment`. 444 * 445 * @param array $prepared_comment The prepared comment data for `wp_insert_comment`. 446 * @param WP_REST_Request $request Request used to insert the comment. 447 */ 448 $prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request ); 449 450 $comment_id = wp_insert_comment( $prepared_comment ); 451 if ( ! $comment_id ) { 452 return new WP_Error( 'rest_comment_failed_create', __( 'Creating comment failed.' ), array( 'status' => 500 ) ); 453 } 454 455 if ( isset( $request['status'] ) ) { 456 $comment = get_comment( $comment_id ); 457 $this->handle_status_param( $request['status'], $comment ); 458 } 459 460 $schema = $this->get_item_schema(); 461 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 462 $meta_update = $this->meta->update_value( $request['meta'], $comment_id ); 463 if ( is_wp_error( $meta_update ) ) { 464 return $meta_update; 465 } 466 } 467 468 $comment = get_comment( $comment_id ); 469 $fields_update = $this->update_additional_fields_for_object( $comment, $request ); 470 if ( is_wp_error( $fields_update ) ) { 471 return $fields_update; 472 } 473 474 $context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view'; 475 $request->set_param( 'context', $context ); 476 $response = $this->prepare_item_for_response( $comment, $request ); 477 $response = rest_ensure_response( $response ); 478 $response->set_status( 201 ); 479 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) ); 480 481 /** 482 * Fires after a comment is created or updated via the REST API. 483 * 484 * @param array $comment Comment as it exists in the database. 485 * @param WP_REST_Request $request The request sent to the API. 486 * @param boolean $creating True when creating a comment, false when updating. 487 */ 488 do_action( 'rest_insert_comment', $comment, $request, true ); 489 490 return $response; 491 } 492 493 /** 494 * Check if a given request has access to update a comment 495 * 496 * @param WP_REST_Request $request Full details about the request. 497 * @return WP_Error|boolean 498 */ 499 public function update_item_permissions_check( $request ) { 500 501 $id = (int) $request['id']; 502 503 $comment = get_comment( $id ); 504 505 if ( $comment && ! $this->check_edit_permission( $comment ) ) { 506 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you can not edit this comment.' ), array( 'status' => rest_authorization_required_code() ) ); 507 } 508 509 return true; 510 } 511 512 /** 513 * Edit a comment 514 * 515 * @param WP_REST_Request $request Full details about the request. 516 * @return WP_Error|WP_REST_Response 517 */ 518 public function update_item( $request ) { 519 $id = (int) $request['id']; 520 521 $comment = get_comment( $id ); 522 if ( empty( $comment ) ) { 523 return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) ); 524 } 525 526 if ( isset( $request['type'] ) && get_comment_type( $id ) !== $request['type'] ) { 527 return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you cannot change the comment type.' ), array( 'status' => 404 ) ); 528 } 529 530 $prepared_args = $this->prepare_item_for_database( $request ); 531 if ( is_wp_error( $prepared_args ) ) { 532 return $prepared_args; 533 } 534 535 if ( empty( $prepared_args ) && isset( $request['status'] ) ) { 536 // Only the comment status is being changed. 537 $change = $this->handle_status_param( $request['status'], $comment ); 538 if ( ! $change ) { 539 return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment status failed.' ), array( 'status' => 500 ) ); 540 } 541 } else { 542 if ( is_wp_error( $prepared_args ) ) { 543 return $prepared_args; 544 } 545 546 $prepared_args['comment_ID'] = $id; 547 548 $updated = wp_update_comment( $prepared_args ); 549 if ( 0 === $updated ) { 550 return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment failed.' ), array( 'status' => 500 ) ); 551 } 552 553 if ( isset( $request['status'] ) ) { 554 $this->handle_status_param( $request['status'], $comment ); 555 } 556 } 557 558 $schema = $this->get_item_schema(); 559 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 560 $meta_update = $this->meta->update_value( $request['meta'], $id ); 561 if ( is_wp_error( $meta_update ) ) { 562 return $meta_update; 563 } 564 } 565 566 $comment = get_comment( $id ); 567 $fields_update = $this->update_additional_fields_for_object( $comment, $request ); 568 if ( is_wp_error( $fields_update ) ) { 569 return $fields_update; 570 } 571 572 $request->set_param( 'context', 'edit' ); 573 $response = $this->prepare_item_for_response( $comment, $request ); 574 575 /* This action is documented in lib/endpoints/class-wp-rest-comments-controller.php */ 576 do_action( 'rest_insert_comment', $comment, $request, false ); 577 578 return rest_ensure_response( $response ); 579 } 580 581 /** 582 * Check if a given request has access to delete a comment 583 * 584 * @param WP_REST_Request $request Full details about the request. 585 * @return WP_Error|boolean 586 */ 587 public function delete_item_permissions_check( $request ) { 588 $id = (int) $request['id']; 589 $comment = get_comment( $id ); 590 if ( ! $comment ) { 591 return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) ); 592 } 593 if ( ! $this->check_edit_permission( $comment ) ) { 594 return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you can not delete this comment.' ), array( 'status' => rest_authorization_required_code() ) ); 595 } 596 return true; 597 } 598 599 /** 600 * Delete a comment. 601 * 602 * @param WP_REST_Request $request Full details about the request. 603 * @return WP_Error|WP_REST_Response 604 */ 605 public function delete_item( $request ) { 606 $id = (int) $request['id']; 607 $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 608 609 $comment = get_comment( $id ); 610 if ( empty( $comment ) ) { 611 return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) ); 612 } 613 614 /** 615 * Filter whether a comment is trashable. 616 * 617 * Return false to disable trash support for the post. 618 * 619 * @param boolean $supports_trash Whether the post type support trashing. 620 * @param WP_Post $comment The comment object being considered for trashing support. 621 */ 622 $supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment ); 623 624 $request->set_param( 'context', 'edit' ); 625 $response = $this->prepare_item_for_response( $comment, $request ); 626 627 if ( $force ) { 628 $result = wp_delete_comment( $comment->comment_ID, true ); 629 } else { 630 // If we don't support trashing for this type, error out 631 if ( ! $supports_trash ) { 632 return new WP_Error( 'rest_trash_not_supported', __( 'The comment does not support trashing.' ), array( 'status' => 501 ) ); 633 } 634 635 if ( 'trash' === $comment->comment_approved ) { 636 return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.' ), array( 'status' => 410 ) ); 637 } 638 639 $result = wp_trash_comment( $comment->comment_ID ); 640 } 641 642 if ( ! $result ) { 643 return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) ); 644 } 645 646 /** 647 * Fires after a comment is deleted via the REST API. 648 * 649 * @param object $comment The deleted comment data. 650 * @param WP_REST_Response $response The response returned from the API. 651 * @param WP_REST_Request $request The request sent to the API. 652 */ 653 do_action( 'rest_delete_comment', $comment, $response, $request ); 654 655 return $response; 656 } 657 658 /** 659 * Prepare a single comment output for response. 660 * 661 * @param object $comment Comment object. 662 * @param WP_REST_Request $request Request object. 663 * @return WP_REST_Response $response 664 */ 665 public function prepare_item_for_response( $comment, $request ) { 666 $data = array( 667 'id' => (int) $comment->comment_ID, 668 'post' => (int) $comment->comment_post_ID, 669 'parent' => (int) $comment->comment_parent, 670 'author' => (int) $comment->user_id, 671 'author_name' => $comment->comment_author, 672 'author_email' => $comment->comment_author_email, 673 'author_url' => $comment->comment_author_url, 674 'author_ip' => $comment->comment_author_IP, 675 'author_user_agent' => $comment->comment_agent, 676 'date' => mysql_to_rfc3339( $comment->comment_date ), 677 'date_gmt' => mysql_to_rfc3339( $comment->comment_date_gmt ), 678 'content' => array( 679 'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment ), 680 'raw' => $comment->comment_content, 681 ), 682 'karma' => (int) $comment->comment_karma, 683 'link' => get_comment_link( $comment ), 684 'status' => $this->prepare_status_response( $comment->comment_approved ), 685 'type' => get_comment_type( $comment->comment_ID ), 686 ); 687 688 $schema = $this->get_item_schema(); 689 690 if ( ! empty( $schema['properties']['author_avatar_urls'] ) ) { 691 $data['author_avatar_urls'] = rest_get_avatar_urls( $comment->comment_author_email ); 692 } 693 694 if ( ! empty( $schema['properties']['meta'] ) ) { 695 $data['meta'] = $this->meta->get_value( $comment->comment_ID, $request ); 696 } 697 698 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 699 $data = $this->add_additional_fields_to_object( $data, $request ); 700 $data = $this->filter_response_by_context( $data, $context ); 701 702 // Wrap the data in a response object 703 $response = rest_ensure_response( $data ); 704 705 $response->add_links( $this->prepare_links( $comment ) ); 706 707 /** 708 * Filter a comment returned from the API. 709 * 710 * Allows modification of the comment right before it is returned. 711 * 712 * @param WP_REST_Response $response The response object. 713 * @param object $comment The original comment object. 714 * @param WP_REST_Request $request Request used to generate the response. 715 */ 716 return apply_filters( 'rest_prepare_comment', $response, $comment, $request ); 717 } 718 719 /** 720 * Prepare links for the request. 721 * 722 * @param object $comment Comment object. 723 * @return array Links for the given comment. 724 */ 725 protected function prepare_links( $comment ) { 726 $links = array( 727 'self' => array( 728 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ), 729 ), 730 'collection' => array( 731 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 732 ), 733 ); 734 735 if ( 0 !== (int) $comment->user_id ) { 736 $links['author'] = array( 737 'href' => rest_url( 'wp/v2/users/' . $comment->user_id ), 738 'embeddable' => true, 739 ); 740 } 741 742 if ( 0 !== (int) $comment->comment_post_ID ) { 743 $post = $this->get_post( $comment->comment_post_ID ); 744 if ( ! empty( $post->ID ) ) { 745 $obj = get_post_type_object( $post->post_type ); 746 $base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; 747 748 $links['up'] = array( 749 'href' => rest_url( 'wp/v2/' . $base . '/' . $comment->comment_post_ID ), 750 'embeddable' => true, 751 'post_type' => $post->post_type, 752 ); 753 } 754 } 755 756 if ( 0 !== (int) $comment->comment_parent ) { 757 $links['in-reply-to'] = array( 758 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ), 759 'embeddable' => true, 760 ); 761 } 762 763 // Only grab one comment to verify the comment has children. 764 $comment_children = $comment->get_children( array( 'number' => 1, 'count' => true ) ); 765 if ( ! empty( $comment_children ) ) { 766 $args = array( 'parent' => $comment->comment_ID ); 767 $rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) ); 768 769 $links['children'] = array( 770 'href' => $rest_url, 771 ); 772 } 773 774 return $links; 775 } 776 777 /** 778 * Prepend internal property prefix to query parameters to match our response fields. 779 * 780 * @param string $query_param 781 * @return string $normalized 782 */ 783 protected function normalize_query_param( $query_param ) { 784 $prefix = 'comment_'; 785 786 switch ( $query_param ) { 787 case 'id': 788 $normalized = $prefix . 'ID'; 789 break; 790 case 'post': 791 $normalized = $prefix . 'post_ID'; 792 break; 793 case 'parent': 794 $normalized = $prefix . 'parent'; 795 break; 796 case 'include': 797 $normalized = 'comment__in'; 798 break; 799 default: 800 $normalized = $prefix . $query_param; 801 break; 802 } 803 804 return $normalized; 805 } 806 807 /** 808 * Check comment_approved to set comment status for single comment output. 809 * 810 * @param string|int $comment_approved 811 * @return string $status 812 */ 813 protected function prepare_status_response( $comment_approved ) { 814 815 switch ( $comment_approved ) { 816 case 'hold': 817 case '0': 818 $status = 'hold'; 819 break; 820 821 case 'approve': 822 case '1': 823 $status = 'approved'; 824 break; 825 826 case 'spam': 827 case 'trash': 828 default: 829 $status = $comment_approved; 830 break; 831 } 832 833 return $status; 834 } 835 836 /** 837 * Prepare a single comment to be inserted into the database. 838 * 839 * @param WP_REST_Request $request Request object. 840 * @return array|WP_Error $prepared_comment 841 */ 842 protected function prepare_item_for_database( $request ) { 843 $prepared_comment = array(); 844 845 /** 846 * Allow the comment_content to be set via the 'content' or 847 * the 'content.raw' properties of the Request object. 848 */ 849 if ( isset( $request['content'] ) && is_string( $request['content'] ) ) { 850 $prepared_comment['comment_content'] = wp_filter_kses( $request['content'] ); 851 } elseif ( isset( $request['content']['raw'] ) && is_string( $request['content']['raw'] ) ) { 852 $prepared_comment['comment_content'] = wp_filter_kses( $request['content']['raw'] ); 853 } 854 855 if ( isset( $request['post'] ) ) { 856 $prepared_comment['comment_post_ID'] = (int) $request['post']; 857 } 858 859 if ( isset( $request['parent'] ) ) { 860 $prepared_comment['comment_parent'] = $request['parent']; 861 } 862 863 if ( isset( $request['author'] ) ) { 864 $user = new WP_User( $request['author'] ); 865 if ( $user->exists() ) { 866 $prepared_comment['user_id'] = $user->ID; 867 $prepared_comment['comment_author'] = $user->display_name; 868 $prepared_comment['comment_author_email'] = $user->user_email; 869 $prepared_comment['comment_author_url'] = $user->user_url; 870 } else { 871 return new WP_Error( 'rest_comment_author_invalid', __( 'Invalid comment author id.' ), array( 'status' => 400 ) ); 872 } 873 } 874 875 if ( isset( $request['author_name'] ) ) { 876 $prepared_comment['comment_author'] = $request['author_name']; 877 } 878 879 if ( isset( $request['author_email'] ) ) { 880 $prepared_comment['comment_author_email'] = $request['author_email']; 881 } 882 883 if ( isset( $request['author_url'] ) ) { 884 $prepared_comment['comment_author_url'] = $request['author_url']; 885 } 886 887 if ( isset( $request['author_ip'] ) ) { 888 $prepared_comment['comment_author_IP'] = $request['author_ip']; 889 } 890 891 if ( isset( $request['type'] ) ) { 892 // Comment type "comment" needs to be created as an empty string. 893 $prepared_comment['comment_type'] = 'comment' === $request['type'] ? '' : $request['type']; 894 } 895 896 if ( isset( $request['karma'] ) ) { 897 $prepared_comment['comment_karma'] = $request['karma'] ; 898 } 899 900 if ( ! empty( $request['date'] ) ) { 901 $date_data = rest_get_date_with_gmt( $request['date'] ); 902 903 if ( ! empty( $date_data ) ) { 904 list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; 905 } 906 } elseif ( ! empty( $request['date_gmt'] ) ) { 907 $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); 908 909 if ( ! empty( $date_data ) ) { 910 list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; 911 } 912 } 913 914 // Require 'comment_content' unless only the 'comment_status' is being 915 // updated. 916 if ( ! empty( $prepared_comment ) && ! isset( $prepared_comment['comment_content'] ) ) { 917 return new WP_Error( 'rest_comment_content_required', __( 'Missing comment content.' ), array( 'status' => 400 ) ); 918 } 919 920 return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request ); 921 } 922 923 /** 924 * Get the Comment's schema, conforming to JSON Schema 925 * 926 * @return array 927 */ 928 public function get_item_schema() { 929 $schema = array( 930 '$schema' => 'http://json-schema.org/draft-04/schema#', 931 'title' => 'comment', 932 'type' => 'object', 933 'properties' => array( 934 'id' => array( 935 'description' => __( 'Unique identifier for the object.' ), 936 'type' => 'integer', 937 'context' => array( 'view', 'edit', 'embed' ), 938 'readonly' => true, 939 ), 940 'author' => array( 941 'description' => __( 'The id of the user object, if author was a user.' ), 942 'type' => 'integer', 943 'context' => array( 'view', 'edit', 'embed' ), 944 ), 945 'author_email' => array( 946 'description' => __( 'Email address for the object author.' ), 947 'type' => 'string', 948 'format' => 'email', 949 'context' => array( 'edit' ), 950 ), 951 'author_ip' => array( 952 'description' => __( 'IP address for the object author.' ), 953 'type' => 'string', 954 'format' => 'ipv4', 955 'context' => array( 'edit' ), 956 'arg_options' => array( 957 'default' => '127.0.0.1', 958 ), 959 ), 960 'author_name' => array( 961 'description' => __( 'Display name for the object author.' ), 962 'type' => 'string', 963 'context' => array( 'view', 'edit', 'embed' ), 964 'arg_options' => array( 965 'sanitize_callback' => 'sanitize_text_field', 966 ), 967 ), 968 'author_url' => array( 969 'description' => __( 'URL for the object author.' ), 970 'type' => 'string', 971 'format' => 'uri', 972 'context' => array( 'view', 'edit', 'embed' ), 973 ), 974 'author_user_agent' => array( 975 'description' => __( 'User agent for the object author.' ), 976 'type' => 'string', 977 'context' => array( 'edit' ), 978 'readonly' => true, 979 ), 980 'content' => array( 981 'description' => __( 'The content for the object.' ), 982 'type' => 'object', 983 'context' => array( 'view', 'edit', 'embed' ), 984 'properties' => array( 985 'raw' => array( 986 'description' => __( 'Content for the object, as it exists in the database.' ), 987 'type' => 'string', 988 'context' => array( 'edit' ), 989 ), 990 'rendered' => array( 991 'description' => __( 'HTML content for the object, transformed for display.' ), 992 'type' => 'string', 993 'context' => array( 'view', 'edit', 'embed' ), 994 ), 995 ), 996 ), 997 'date' => array( 998 'description' => __( 'The date the object was published.' ), 999 'type' => 'string', 1000 'format' => 'date-time', 1001 'context' => array( 'view', 'edit', 'embed' ), 1002 ), 1003 'date_gmt' => array( 1004 'description' => __( 'The date the object was published as GMT.' ), 1005 'type' => 'string', 1006 'format' => 'date-time', 1007 'context' => array( 'view', 'edit' ), 1008 ), 1009 'karma' => array( 1010 'description' => __( 'Karma for the object.' ), 1011 'type' => 'integer', 1012 'context' => array( 'edit' ), 1013 ), 1014 'link' => array( 1015 'description' => __( 'URL to the object.' ), 1016 'type' => 'string', 1017 'format' => 'uri', 1018 'context' => array( 'view', 'edit', 'embed' ), 1019 'readonly' => true, 1020 ), 1021 'parent' => array( 1022 'description' => __( 'The id for the parent of the object.' ), 1023 'type' => 'integer', 1024 'context' => array( 'view', 'edit', 'embed' ), 1025 'arg_options' => array( 1026 'default' => 0, 1027 ), 1028 ), 1029 'post' => array( 1030 'description' => __( 'The id of the associated post object.' ), 1031 'type' => 'integer', 1032 'context' => array( 'view', 'edit' ), 1033 'arg_options' => array( 1034 'default' => 0, 1035 ), 1036 ), 1037 'status' => array( 1038 'description' => __( 'State of the object.' ), 1039 'type' => 'string', 1040 'context' => array( 'view', 'edit' ), 1041 'arg_options' => array( 1042 'sanitize_callback' => 'sanitize_key', 1043 ), 1044 ), 1045 'type' => array( 1046 'description' => __( 'Type of Comment for the object.' ), 1047 'type' => 'string', 1048 'context' => array( 'view', 'edit', 'embed' ), 1049 'default' => 'comment', 1050 'arg_options' => array( 1051 'sanitize_callback' => 'sanitize_key', 1052 ), 1053 ), 1054 ), 1055 ); 1056 1057 if ( get_option( 'show_avatars' ) ) { 1058 $avatar_properties = array(); 1059 1060 $avatar_sizes = rest_get_avatar_sizes(); 1061 foreach ( $avatar_sizes as $size ) { 1062 $avatar_properties[ $size ] = array( 1063 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ), 1064 'type' => 'string', 1065 'format' => 'uri', 1066 'context' => array( 'embed', 'view', 'edit' ), 1067 ); 1068 } 1069 1070 $schema['properties']['author_avatar_urls'] = array( 1071 'description' => __( 'Avatar URLs for the object author.' ), 1072 'type' => 'object', 1073 'context' => array( 'view', 'edit', 'embed' ), 1074 'readonly' => true, 1075 'properties' => $avatar_properties, 1076 ); 1077 } 1078 1079 $schema['properties']['meta'] = $this->meta->get_field_schema(); 1080 1081 return $this->add_additional_fields_schema( $schema ); 1082 } 1083 1084 /** 1085 * Get the query params for collections 1086 * 1087 * @return array 1088 */ 1089 public function get_collection_params() { 1090 $query_params = parent::get_collection_params(); 1091 1092 $query_params['context']['default'] = 'view'; 1093 1094 $query_params['after'] = array( 1095 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.' ), 1096 'type' => 'string', 1097 'format' => 'date-time', 1098 'validate_callback' => 'rest_validate_request_arg', 1099 ); 1100 $query_params['author'] = array( 1101 'description' => __( 'Limit result set to comments assigned to specific user ids. Requires authorization.' ), 1102 'sanitize_callback' => 'wp_parse_id_list', 1103 'type' => 'array', 1104 ); 1105 $query_params['author_exclude'] = array( 1106 'description' => __( 'Ensure result set excludes comments assigned to specific user ids. Requires authorization.' ), 1107 'sanitize_callback' => 'wp_parse_id_list', 1108 'type' => 'array', 1109 ); 1110 $query_params['author_email'] = array( 1111 'default' => null, 1112 'description' => __( 'Limit result set to that from a specific author email. Requires authorization.' ), 1113 'format' => 'email', 1114 'sanitize_callback' => 'sanitize_email', 1115 'type' => 'string', 1116 ); 1117 $query_params['before'] = array( 1118 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.' ), 1119 'type' => 'string', 1120 'format' => 'date-time', 1121 'validate_callback' => 'rest_validate_request_arg', 1122 ); 1123 $query_params['exclude'] = array( 1124 'description' => __( 'Ensure result set excludes specific ids.' ), 1125 'type' => 'array', 1126 'default' => array(), 1127 'sanitize_callback' => 'wp_parse_id_list', 1128 ); 1129 $query_params['include'] = array( 1130 'description' => __( 'Limit result set to specific ids.' ), 1131 'type' => 'array', 1132 'default' => array(), 1133 'sanitize_callback' => 'wp_parse_id_list', 1134 ); 1135 $query_params['karma'] = array( 1136 'default' => null, 1137 'description' => __( 'Limit result set to that of a particular comment karma. Requires authorization.' ), 1138 'sanitize_callback' => 'absint', 1139 'type' => 'integer', 1140 'validate_callback' => 'rest_validate_request_arg', 1141 ); 1142 $query_params['offset'] = array( 1143 'description' => __( 'Offset the result set by a specific number of comments.' ), 1144 'type' => 'integer', 1145 'sanitize_callback' => 'absint', 1146 'validate_callback' => 'rest_validate_request_arg', 1147 ); 1148 $query_params['order'] = array( 1149 'description' => __( 'Order sort attribute ascending or descending.' ), 1150 'type' => 'string', 1151 'sanitize_callback' => 'sanitize_key', 1152 'validate_callback' => 'rest_validate_request_arg', 1153 'default' => 'desc', 1154 'enum' => array( 1155 'asc', 1156 'desc', 1157 ), 1158 ); 1159 $query_params['orderby'] = array( 1160 'description' => __( 'Sort collection by object attribute.' ), 1161 'type' => 'string', 1162 'sanitize_callback' => 'sanitize_key', 1163 'validate_callback' => 'rest_validate_request_arg', 1164 'default' => 'date_gmt', 1165 'enum' => array( 1166 'date', 1167 'date_gmt', 1168 'id', 1169 'include', 1170 'post', 1171 'parent', 1172 'type', 1173 ), 1174 ); 1175 $query_params['parent'] = array( 1176 'default' => array(), 1177 'description' => __( 'Limit result set to resources of specific parent ids.' ), 1178 'sanitize_callback' => 'wp_parse_id_list', 1179 'type' => 'array', 1180 ); 1181 $query_params['parent_exclude'] = array( 1182 'default' => array(), 1183 'description' => __( 'Ensure result set excludes specific parent ids.' ), 1184 'sanitize_callback' => 'wp_parse_id_list', 1185 'type' => 'array', 1186 ); 1187 $query_params['post'] = array( 1188 'default' => array(), 1189 'description' => __( 'Limit result set to resources assigned to specific post ids.' ), 1190 'type' => 'array', 1191 'sanitize_callback' => 'wp_parse_id_list', 1192 ); 1193 $query_params['status'] = array( 1194 'default' => 'approve', 1195 'description' => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ), 1196 'sanitize_callback' => 'sanitize_key', 1197 'type' => 'string', 1198 'validate_callback' => 'rest_validate_request_arg', 1199 ); 1200 $query_params['type'] = array( 1201 'default' => 'comment', 1202 'description' => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ), 1203 'sanitize_callback' => 'sanitize_key', 1204 'type' => 'string', 1205 'validate_callback' => 'rest_validate_request_arg', 1206 ); 1207 return $query_params; 1208 } 1209 1210 /** 1211 * Set the comment_status of a given comment object when creating or updating a comment. 1212 * 1213 * @param string|int $new_status 1214 * @param object $comment 1215 * @return boolean $changed 1216 */ 1217 protected function handle_status_param( $new_status, $comment ) { 1218 $old_status = wp_get_comment_status( $comment->comment_ID ); 1219 1220 if ( $new_status === $old_status ) { 1221 return false; 1222 } 1223 1224 switch ( $new_status ) { 1225 case 'approved' : 1226 case 'approve': 1227 case '1': 1228 $changed = wp_set_comment_status( $comment->comment_ID, 'approve' ); 1229 break; 1230 case 'hold': 1231 case '0': 1232 $changed = wp_set_comment_status( $comment->comment_ID, 'hold' ); 1233 break; 1234 case 'spam' : 1235 $changed = wp_spam_comment( $comment->comment_ID ); 1236 break; 1237 case 'unspam' : 1238 $changed = wp_unspam_comment( $comment->comment_ID ); 1239 break; 1240 case 'trash' : 1241 $changed = wp_trash_comment( $comment->comment_ID ); 1242 break; 1243 case 'untrash' : 1244 $changed = wp_untrash_comment( $comment->comment_ID ); 1245 break; 1246 default : 1247 $changed = false; 1248 break; 1249 } 1250 1251 return $changed; 1252 } 1253 1254 /** 1255 * Check if we can read a post. 1256 * 1257 * Correctly handles posts with the inherit status. 1258 * 1259 * @param WP_Post $post Post Object. 1260 * @return boolean Can we read it? 1261 */ 1262 protected function check_read_post_permission( $post ) { 1263 $posts_controller = new WP_REST_Posts_Controller( $post->post_type ); 1264 1265 return $posts_controller->check_read_permission( $post ); 1266 } 1267 1268 /** 1269 * Check if we can read a comment. 1270 * 1271 * @param object $comment Comment object. 1272 * @return boolean Can we read it? 1273 */ 1274 protected function check_read_permission( $comment ) { 1275 if ( ! empty( $comment->comment_post_ID ) ) { 1276 $post = get_post( $comment->comment_post_ID ); 1277 if ( $post ) { 1278 if ( $this->check_read_post_permission( $post ) && 1 === (int) $comment->comment_approved ) { 1279 return true; 1280 } 1281 } 1282 } 1283 1284 if ( 0 === get_current_user_id() ) { 1285 return false; 1286 } 1287 1288 if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) { 1289 return false; 1290 } 1291 1292 if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) { 1293 return true; 1294 } 1295 1296 return current_user_can( 'edit_comment', $comment->comment_ID ); 1297 } 1298 1299 /** 1300 * Check if we can edit or delete a comment. 1301 * 1302 * @param object $comment Comment object. 1303 * @return boolean Can we edit or delete it? 1304 */ 1305 protected function check_edit_permission( $comment ) { 1306 if ( 0 === (int) get_current_user_id() ) { 1307 return false; 1308 } 1309 1310 if ( ! current_user_can( 'moderate_comments' ) ) { 1311 return false; 1312 } 1313 1314 return current_user_can( 'edit_comment', $comment->comment_ID ); 1315 } 1316 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 4 abstract class WP_REST_Controller { 5 6 /** 7 * The namespace of this controller's route. 8 * 9 * @var string 10 */ 11 protected $namespace; 12 13 /** 14 * The base of this controller's route. 15 * 16 * @var string 17 */ 18 protected $rest_base; 19 20 /** 21 * Register the routes for the objects of the controller. 22 */ 23 public function register_routes() { 24 _doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overridden' ), 'WPAPI-2.0' ); 25 } 26 27 /** 28 * Check if a given request has access to get items. 29 * 30 * @param WP_REST_Request $request Full data about the request. 31 * @return WP_Error|boolean 32 */ 33 public function get_items_permissions_check( $request ) { 34 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 35 } 36 37 /** 38 * Get a collection of items. 39 * 40 * @param WP_REST_Request $request Full data about the request. 41 * @return WP_Error|WP_REST_Response 42 */ 43 public function get_items( $request ) { 44 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 45 } 46 47 /** 48 * Check if a given request has access to get a specific item. 49 * 50 * @param WP_REST_Request $request Full data about the request. 51 * @return WP_Error|boolean 52 */ 53 public function get_item_permissions_check( $request ) { 54 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 55 } 56 57 /** 58 * Get one item from the collection. 59 * 60 * @param WP_REST_Request $request Full data about the request. 61 * @return WP_Error|WP_REST_Response 62 */ 63 public function get_item( $request ) { 64 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 65 } 66 67 /** 68 * Check if a given request has access to create items. 69 * 70 * @param WP_REST_Request $request Full data about the request. 71 * @return WP_Error|boolean 72 */ 73 public function create_item_permissions_check( $request ) { 74 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 75 } 76 77 /** 78 * Create one item from the collection. 79 * 80 * @param WP_REST_Request $request Full data about the request. 81 * @return WP_Error|WP_REST_Response 82 */ 83 public function create_item( $request ) { 84 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 85 } 86 87 /** 88 * Check if a given request has access to update a specific item. 89 * 90 * @param WP_REST_Request $request Full data about the request. 91 * @return WP_Error|boolean 92 */ 93 public function update_item_permissions_check( $request ) { 94 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 95 } 96 97 /** 98 * Update one item from the collection. 99 * 100 * @param WP_REST_Request $request Full data about the request. 101 * @return WP_Error|WP_REST_Response 102 */ 103 public function update_item( $request ) { 104 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 105 } 106 107 /** 108 * Check if a given request has access to delete a specific item. 109 * 110 * @param WP_REST_Request $request Full data about the request. 111 * @return WP_Error|boolean 112 */ 113 public function delete_item_permissions_check( $request ) { 114 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 115 } 116 117 /** 118 * Delete one item from the collection. 119 * 120 * @param WP_REST_Request $request Full data about the request. 121 * @return WP_Error|WP_REST_Response 122 */ 123 public function delete_item( $request ) { 124 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 125 } 126 127 /** 128 * Prepare the item for create or update operation. 129 * 130 * @param WP_REST_Request $request Request object. 131 * @return WP_Error|object $prepared_item 132 */ 133 protected function prepare_item_for_database( $request ) { 134 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 135 } 136 137 /** 138 * Prepare the item for the REST response. 139 * 140 * @param mixed $item WordPress representation of the item. 141 * @param WP_REST_Request $request Request object. 142 * @return WP_Error|WP_REST_Response $response 143 */ 144 public function prepare_item_for_response( $item, $request ) { 145 return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); 146 } 147 148 /** 149 * Prepare a response for inserting into a collection. 150 * 151 * @param WP_REST_Response $response Response object. 152 * @return array Response data, ready for insertion into collection data. 153 */ 154 public function prepare_response_for_collection( $response ) { 155 if ( ! ( $response instanceof WP_REST_Response ) ) { 156 return $response; 157 } 158 159 $data = (array) $response->get_data(); 160 $server = rest_get_server(); 161 162 if ( method_exists( $server, 'get_compact_response_links' ) ) { 163 $links = call_user_func( array( $server, 'get_compact_response_links' ), $response ); 164 } else { 165 $links = call_user_func( array( $server, 'get_response_links' ), $response ); 166 } 167 168 if ( ! empty( $links ) ) { 169 $data['_links'] = $links; 170 } 171 172 return $data; 173 } 174 175 /** 176 * Filter a response based on the context defined in the schema. 177 * 178 * @param array $data 179 * @param string $context 180 * @return array 181 */ 182 public function filter_response_by_context( $data, $context ) { 183 184 $schema = $this->get_item_schema(); 185 foreach ( $data as $key => $value ) { 186 if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) { 187 continue; 188 } 189 190 if ( ! in_array( $context, $schema['properties'][ $key ]['context'], true ) ) { 191 unset( $data[ $key ] ); 192 continue; 193 } 194 195 if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) { 196 foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) { 197 if ( empty( $details['context'] ) ) { 198 continue; 199 } 200 if ( ! in_array( $context, $details['context'], true ) ) { 201 if ( isset( $data[ $key ][ $attribute ] ) ) { 202 unset( $data[ $key ][ $attribute ] ); 203 } 204 } 205 } 206 } 207 } 208 209 return $data; 210 } 211 212 /** 213 * Get the item's schema, conforming to JSON Schema. 214 * 215 * @return array 216 */ 217 public function get_item_schema() { 218 return $this->add_additional_fields_schema( array() ); 219 } 220 221 /** 222 * Get the item's schema for display / public consumption purposes. 223 * 224 * @return array 225 */ 226 public function get_public_item_schema() { 227 228 $schema = $this->get_item_schema(); 229 230 foreach ( $schema['properties'] as &$property ) { 231 unset( $property['arg_options'] ); 232 } 233 234 return $schema; 235 } 236 237 /** 238 * Get the query params for collections. 239 * 240 * @return array 241 */ 242 public function get_collection_params() { 243 return array( 244 'context' => $this->get_context_param(), 245 'page' => array( 246 'description' => __( 'Current page of the collection.' ), 247 'type' => 'integer', 248 'default' => 1, 249 'sanitize_callback' => 'absint', 250 'validate_callback' => 'rest_validate_request_arg', 251 'minimum' => 1, 252 ), 253 'per_page' => array( 254 'description' => __( 'Maximum number of items to be returned in result set.' ), 255 'type' => 'integer', 256 'default' => 10, 257 'minimum' => 1, 258 'maximum' => 100, 259 'sanitize_callback' => 'absint', 260 'validate_callback' => 'rest_validate_request_arg', 261 ), 262 'search' => array( 263 'description' => __( 'Limit results to those matching a string.' ), 264 'type' => 'string', 265 'sanitize_callback' => 'sanitize_text_field', 266 'validate_callback' => 'rest_validate_request_arg', 267 ), 268 ); 269 } 270 271 /** 272 * Get the magical context param. 273 * 274 * Ensures consistent description between endpoints, and populates enum from schema. 275 * 276 * @param array $args 277 * @return array 278 */ 279 public function get_context_param( $args = array() ) { 280 $param_details = array( 281 'description' => __( 'Scope under which the request is made; determines fields present in response.' ), 282 'type' => 'string', 283 'sanitize_callback' => 'sanitize_key', 284 'validate_callback' => 'rest_validate_request_arg', 285 ); 286 $schema = $this->get_item_schema(); 287 if ( empty( $schema['properties'] ) ) { 288 return array_merge( $param_details, $args ); 289 } 290 $contexts = array(); 291 foreach ( $schema['properties'] as $attributes ) { 292 if ( ! empty( $attributes['context'] ) ) { 293 $contexts = array_merge( $contexts, $attributes['context'] ); 294 } 295 } 296 if ( ! empty( $contexts ) ) { 297 $param_details['enum'] = array_unique( $contexts ); 298 rsort( $param_details['enum'] ); 299 } 300 return array_merge( $param_details, $args ); 301 } 302 303 /** 304 * Add the values from additional fields to a data object. 305 * 306 * @param array $object 307 * @param WP_REST_Request $request 308 * @return array modified object with additional fields. 309 */ 310 protected function add_additional_fields_to_object( $object, $request ) { 311 312 $additional_fields = $this->get_additional_fields(); 313 314 foreach ( $additional_fields as $field_name => $field_options ) { 315 316 if ( ! $field_options['get_callback'] ) { 317 continue; 318 } 319 320 $object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() ); 321 } 322 323 return $object; 324 } 325 326 /** 327 * Update the values of additional fields added to a data object. 328 * 329 * @param array $object 330 * @param WP_REST_Request $request 331 * @return bool|WP_Error True on success, WP_Error object if a field cannot be updated. 332 */ 333 protected function update_additional_fields_for_object( $object, $request ) { 334 $additional_fields = $this->get_additional_fields(); 335 336 foreach ( $additional_fields as $field_name => $field_options ) { 337 if ( ! $field_options['update_callback'] ) { 338 continue; 339 } 340 341 // Don't run the update callbacks if the data wasn't passed in the request. 342 if ( ! isset( $request[ $field_name ] ) ) { 343 continue; 344 } 345 346 $result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() ); 347 if ( is_wp_error( $result ) ) { 348 return $result; 349 } 350 } 351 352 return true; 353 } 354 355 /** 356 * Add the schema from additional fields to an schema array. 357 * 358 * The type of object is inferred from the passed schema. 359 * 360 * @param array $schema Schema array. 361 * @return array Modified Schema array. 362 */ 363 protected function add_additional_fields_schema( $schema ) { 364 if ( empty( $schema['title'] ) ) { 365 return $schema; 366 } 367 368 /** 369 * Can't use $this->get_object_type otherwise we cause an inf loop. 370 */ 371 $object_type = $schema['title']; 372 373 $additional_fields = $this->get_additional_fields( $object_type ); 374 375 foreach ( $additional_fields as $field_name => $field_options ) { 376 if ( ! $field_options['schema'] ) { 377 continue; 378 } 379 380 $schema['properties'][ $field_name ] = $field_options['schema']; 381 } 382 383 return $schema; 384 } 385 386 /** 387 * Get all the registered additional fields for a given object-type. 388 * 389 * @param string $object_type 390 * @return array 391 */ 392 protected function get_additional_fields( $object_type = null ) { 393 394 if ( ! $object_type ) { 395 $object_type = $this->get_object_type(); 396 } 397 398 if ( ! $object_type ) { 399 return array(); 400 } 401 402 global $wp_rest_additional_fields; 403 404 if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) { 405 return array(); 406 } 407 408 return $wp_rest_additional_fields[ $object_type ]; 409 } 410 411 /** 412 * Get the object type this controller is responsible for managing. 413 * 414 * @return string 415 */ 416 protected function get_object_type() { 417 $schema = $this->get_item_schema(); 418 419 if ( ! $schema || ! isset( $schema['title'] ) ) { 420 return null; 421 } 422 423 return $schema['title']; 424 } 425 426 /** 427 * Get an array of endpoint arguments from the item schema for the controller. 428 * 429 * @param string $method HTTP method of the request. The arguments 430 * for `CREATABLE` requests are checked for required 431 * values and may fall-back to a given default, this 432 * is not done on `EDITABLE` requests. Default is 433 * WP_REST_Server::CREATABLE. 434 * @return array $endpoint_args 435 */ 436 public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { 437 438 $schema = $this->get_item_schema(); 439 $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array(); 440 $endpoint_args = array(); 441 442 foreach ( $schema_properties as $field_id => $params ) { 443 444 // Arguments specified as `readonly` are not allowed to be set. 445 if ( ! empty( $params['readonly'] ) ) { 446 continue; 447 } 448 449 $endpoint_args[ $field_id ] = array( 450 'validate_callback' => 'rest_validate_request_arg', 451 'sanitize_callback' => 'rest_sanitize_request_arg', 452 ); 453 454 if ( isset( $params['description'] ) ) { 455 $endpoint_args[ $field_id ]['description'] = $params['description']; 456 } 457 458 if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) { 459 $endpoint_args[ $field_id ]['default'] = $params['default']; 460 } 461 462 if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) { 463 $endpoint_args[ $field_id ]['required'] = true; 464 } 465 466 foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) { 467 if ( isset( $params[ $schema_prop ] ) ) { 468 $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ]; 469 } 470 } 471 472 // Merge in any options provided by the schema property. 473 if ( isset( $params['arg_options'] ) ) { 474 475 // Only use required / default from arg_options on CREATABLE endpoints. 476 if ( WP_REST_Server::CREATABLE !== $method ) { 477 $params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) ); 478 } 479 480 $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] ); 481 } 482 } 483 484 return $endpoint_args; 485 } 486 487 /** 488 * Retrieves post data given a post ID or post object. 489 * 490 * This is a subset of the functionality of the `get_post()` function, with 491 * the additional functionality of having `the_post` action done on the 492 * resultant post object. This is done so that plugins may manipulate the 493 * post that is used in the REST API. 494 * 495 * @see get_post() 496 * @global WP_Query $wp_query 497 * 498 * @param int|WP_Post $post Post ID or post object. Defaults to global $post. 499 * @return WP_Post|null A `WP_Post` object when successful. 500 */ 501 public function get_post( $post ) { 502 $post_obj = get_post( $post ); 503 504 /** 505 * Filter the post. 506 * 507 * Allows plugins to filter the post object as returned by `\WP_REST_Controller::get_post()`. 508 * 509 * @param WP_Post|null $post_obj The post object as returned by `get_post()`. 510 * @param int|WP_Post $post The original value used to obtain the post object. 511 */ 512 $post = apply_filters( 'rest_the_post', $post_obj, $post ); 513 514 return $post; 515 } 516 517 /** 518 * Sanitize the slug value. 519 * 520 * @internal We can't use {@see sanitize_title} directly, as the second 521 * parameter is the fallback title, which would end up being set to the 522 * request object. 523 * @see https://github.com/WP-API/WP-API/issues/1585 524 * 525 * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659 526 * 527 * @param string $slug Slug value passed in request. 528 * @return string Sanitized value for the slug. 529 */ 530 public function sanitize_slug( $slug ) { 531 return sanitize_title( $slug ); 532 } 533 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 class WP_REST_Post_Statuses_Controller extends WP_REST_Controller { 4 5 public function __construct() { 6 $this->namespace = 'wp/v2'; 7 $this->rest_base = 'statuses'; 8 } 9 10 /** 11 * Register the routes for the objects of the controller. 12 */ 13 public function register_routes() { 14 15 register_rest_route( $this->namespace, '/' . $this->rest_base, array( 16 array( 17 'methods' => WP_REST_Server::READABLE, 18 'callback' => array( $this, 'get_items' ), 19 'permission_callback' => array( $this, 'get_items_permissions_check' ), 20 'args' => $this->get_collection_params(), 21 ), 22 'schema' => array( $this, 'get_public_item_schema' ), 23 ) ); 24 25 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<status>[\w-]+)', array( 26 array( 27 'methods' => WP_REST_Server::READABLE, 28 'callback' => array( $this, 'get_item' ), 29 'permission_callback' => array( $this, 'get_item_permissions_check' ), 30 'args' => array( 31 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 32 ), 33 ), 34 'schema' => array( $this, 'get_public_item_schema' ), 35 ) ); 36 } 37 38 /** 39 * Check whether a given request has permission to read post statuses. 40 * 41 * @param WP_REST_Request $request Full details about the request. 42 * @return WP_Error|boolean 43 */ 44 public function get_items_permissions_check( $request ) { 45 if ( 'edit' === $request['context'] ) { 46 $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); 47 foreach ( $types as $type ) { 48 if ( current_user_can( $type->cap->edit_posts ) ) { 49 return true; 50 } 51 } 52 return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 53 } 54 return true; 55 } 56 57 /** 58 * Get all post statuses, depending on user context 59 * 60 * @param WP_REST_Request $request 61 * @return array|WP_Error 62 */ 63 public function get_items( $request ) { 64 $data = array(); 65 $statuses = get_post_stati( array( 'internal' => false ), 'object' ); 66 $statuses['trash'] = get_post_status_object( 'trash' ); 67 foreach ( $statuses as $slug => $obj ) { 68 $ret = $this->check_read_permission( $obj ); 69 if ( ! $ret ) { 70 continue; 71 } 72 $status = $this->prepare_item_for_response( $obj, $request ); 73 $data[ $obj->name ] = $this->prepare_response_for_collection( $status ); 74 } 75 return rest_ensure_response( $data ); 76 } 77 78 /** 79 * Check if a given request has access to read a post status. 80 * 81 * @param WP_REST_Request $request Full details about the request. 82 * @return WP_Error|boolean 83 */ 84 public function get_item_permissions_check( $request ) { 85 $status = get_post_status_object( $request['status'] ); 86 if ( empty( $status ) ) { 87 return new WP_Error( 'rest_status_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) ); 88 } 89 $check = $this->check_read_permission( $status ); 90 if ( ! $check ) { 91 return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view resource.' ), array( 'status' => rest_authorization_required_code() ) ); 92 } 93 return true; 94 } 95 96 /** 97 * Check whether a given post status should be visible 98 * 99 * @param object $status 100 * @return boolean 101 */ 102 protected function check_read_permission( $status ) { 103 if ( true === $status->public ) { 104 return true; 105 } 106 if ( false === $status->internal || 'trash' === $status->name ) { 107 $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); 108 foreach ( $types as $type ) { 109 if ( current_user_can( $type->cap->edit_posts ) ) { 110 return true; 111 } 112 } 113 } 114 return false; 115 } 116 117 /** 118 * Get a specific post status 119 * 120 * @param WP_REST_Request $request 121 * @return array|WP_Error 122 */ 123 public function get_item( $request ) { 124 $obj = get_post_status_object( $request['status'] ); 125 if ( empty( $obj ) ) { 126 return new WP_Error( 'rest_status_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) ); 127 } 128 $data = $this->prepare_item_for_response( $obj, $request ); 129 return rest_ensure_response( $data ); 130 } 131 132 /** 133 * Prepare a post status object for serialization 134 * 135 * @param stdClass $status Post status data 136 * @param WP_REST_Request $request 137 * @return WP_REST_Response Post status data 138 */ 139 public function prepare_item_for_response( $status, $request ) { 140 141 $data = array( 142 'name' => $status->label, 143 'private' => (bool) $status->private, 144 'protected' => (bool) $status->protected, 145 'public' => (bool) $status->public, 146 'queryable' => (bool) $status->publicly_queryable, 147 'show_in_list' => (bool) $status->show_in_admin_all_list, 148 'slug' => $status->name, 149 ); 150 151 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 152 $data = $this->add_additional_fields_to_object( $data, $request ); 153 $data = $this->filter_response_by_context( $data, $context ); 154 155 $response = rest_ensure_response( $data ); 156 157 if ( 'publish' === $status->name ) { 158 $response->add_link( 'archives', rest_url( 'wp/v2/posts' ) ); 159 } else { 160 $response->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( 'wp/v2/posts' ) ) ); 161 } 162 163 /** 164 * Filter a status returned from the API. 165 * 166 * Allows modification of the status data right before it is returned. 167 * 168 * @param WP_REST_Response $response The response object. 169 * @param object $status The original status object. 170 * @param WP_REST_Request $request Request used to generate the response. 171 */ 172 return apply_filters( 'rest_prepare_status', $response, $status, $request ); 173 } 174 175 /** 176 * Get the Post status' schema, conforming to JSON Schema 177 * 178 * @return array 179 */ 180 public function get_item_schema() { 181 $schema = array( 182 '$schema' => 'http://json-schema.org/draft-04/schema#', 183 'title' => 'status', 184 'type' => 'object', 185 'properties' => array( 186 'name' => array( 187 'description' => __( 'The title for the resource.' ), 188 'type' => 'string', 189 'context' => array( 'embed', 'view', 'edit' ), 190 'readonly' => true, 191 ), 192 'private' => array( 193 'description' => __( 'Whether posts with this resource should be private.' ), 194 'type' => 'boolean', 195 'context' => array( 'edit' ), 196 'readonly' => true, 197 ), 198 'protected' => array( 199 'description' => __( 'Whether posts with this resource should be protected.' ), 200 'type' => 'boolean', 201 'context' => array( 'edit' ), 202 'readonly' => true, 203 ), 204 'public' => array( 205 'description' => __( 'Whether posts of this resource should be shown in the front end of the site.' ), 206 'type' => 'boolean', 207 'context' => array( 'view', 'edit' ), 208 'readonly' => true, 209 ), 210 'queryable' => array( 211 'description' => __( 'Whether posts with this resource should be publicly-queryable.' ), 212 'type' => 'boolean', 213 'context' => array( 'view', 'edit' ), 214 'readonly' => true, 215 ), 216 'show_in_list' => array( 217 'description' => __( 'Whether to include posts in the edit listing for their post type.' ), 218 'type' => 'boolean', 219 'context' => array( 'edit' ), 220 'readonly' => true, 221 ), 222 'slug' => array( 223 'description' => __( 'An alphanumeric identifier for the resource.' ), 224 'type' => 'string', 225 'context' => array( 'embed', 'view', 'edit' ), 226 'readonly' => true, 227 ), 228 ), 229 ); 230 return $this->add_additional_fields_schema( $schema ); 231 } 232 233 /** 234 * Get the query params for collections 235 * 236 * @return array 237 */ 238 public function get_collection_params() { 239 return array( 240 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 241 ); 242 } 243 244 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 class WP_REST_Post_Types_Controller extends WP_REST_Controller { 4 5 public function __construct() { 6 $this->namespace = 'wp/v2'; 7 $this->rest_base = 'types'; 8 } 9 10 /** 11 * Register the routes for the objects of the controller. 12 */ 13 public function register_routes() { 14 15 register_rest_route( $this->namespace, '/' . $this->rest_base, array( 16 array( 17 'methods' => WP_REST_Server::READABLE, 18 'callback' => array( $this, 'get_items' ), 19 'permission_callback' => array( $this, 'get_items_permissions_check' ), 20 'args' => $this->get_collection_params(), 21 ), 22 'schema' => array( $this, 'get_public_item_schema' ), 23 ) ); 24 25 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<type>[\w-]+)', array( 26 array( 27 'methods' => WP_REST_Server::READABLE, 28 'callback' => array( $this, 'get_item' ), 29 'args' => array( 30 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 31 ), 32 ), 33 'schema' => array( $this, 'get_public_item_schema' ), 34 ) ); 35 } 36 37 /** 38 * Check whether a given request has permission to read types. 39 * 40 * @param WP_REST_Request $request Full details about the request. 41 * @return WP_Error|boolean 42 */ 43 public function get_items_permissions_check( $request ) { 44 if ( 'edit' === $request['context'] ) { 45 foreach ( get_post_types( array(), 'object' ) as $post_type ) { 46 if ( ! empty( $post_type->show_in_rest ) && current_user_can( $post_type->cap->edit_posts ) ) { 47 return true; 48 } 49 } 50 return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 51 } 52 return true; 53 } 54 55 /** 56 * Get all public post types 57 * 58 * @param WP_REST_Request $request 59 * @return array|WP_Error 60 */ 61 public function get_items( $request ) { 62 $data = array(); 63 foreach ( get_post_types( array(), 'object' ) as $obj ) { 64 if ( empty( $obj->show_in_rest ) || ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) ) { 65 continue; 66 } 67 $post_type = $this->prepare_item_for_response( $obj, $request ); 68 $data[ $obj->name ] = $this->prepare_response_for_collection( $post_type ); 69 } 70 return rest_ensure_response( $data ); 71 } 72 73 /** 74 * Get a specific post type 75 * 76 * @param WP_REST_Request $request 77 * @return array|WP_Error 78 */ 79 public function get_item( $request ) { 80 $obj = get_post_type_object( $request['type'] ); 81 if ( empty( $obj ) ) { 82 return new WP_Error( 'rest_type_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) ); 83 } 84 if ( empty( $obj->show_in_rest ) ) { 85 return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view resource.' ), array( 'status' => rest_authorization_required_code() ) ); 86 } 87 if ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) { 88 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this resource.' ), array( 'status' => rest_authorization_required_code() ) ); 89 } 90 $data = $this->prepare_item_for_response( $obj, $request ); 91 return rest_ensure_response( $data ); 92 } 93 94 /** 95 * Prepare a post type object for serialization 96 * 97 * @param stdClass $post_type Post type data 98 * @param WP_REST_Request $request 99 * @return WP_REST_Response $response 100 */ 101 public function prepare_item_for_response( $post_type, $request ) { 102 $data = array( 103 'capabilities' => $post_type->cap, 104 'description' => $post_type->description, 105 'hierarchical' => $post_type->hierarchical, 106 'labels' => $post_type->labels, 107 'name' => $post_type->label, 108 'slug' => $post_type->name, 109 ); 110 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 111 $data = $this->add_additional_fields_to_object( $data, $request ); 112 $data = $this->filter_response_by_context( $data, $context ); 113 114 // Wrap the data in a response object. 115 $response = rest_ensure_response( $data ); 116 117 $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; 118 $response->add_links( array( 119 'collection' => array( 120 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 121 ), 122 'https://api.w.org/items' => array( 123 'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ), 124 ), 125 ) ); 126 127 /** 128 * Filter a post type returned from the API. 129 * 130 * Allows modification of the post type data right before it is returned. 131 * 132 * @param WP_REST_Response $response The response object. 133 * @param object $item The original post type object. 134 * @param WP_REST_Request $request Request used to generate the response. 135 */ 136 return apply_filters( 'rest_prepare_post_type', $response, $post_type, $request ); 137 } 138 139 /** 140 * Get the Post type's schema, conforming to JSON Schema 141 * 142 * @return array 143 */ 144 public function get_item_schema() { 145 $schema = array( 146 '$schema' => 'http://json-schema.org/draft-04/schema#', 147 'title' => 'type', 148 'type' => 'object', 149 'properties' => array( 150 'capabilities' => array( 151 'description' => __( 'All capabilities used by the resource.' ), 152 'type' => 'array', 153 'context' => array( 'edit' ), 154 'readonly' => true, 155 ), 156 'description' => array( 157 'description' => __( 'A human-readable description of the resource.' ), 158 'type' => 'string', 159 'context' => array( 'view', 'edit' ), 160 'readonly' => true, 161 ), 162 'hierarchical' => array( 163 'description' => __( 'Whether or not the resource should have children.' ), 164 'type' => 'boolean', 165 'context' => array( 'view', 'edit' ), 166 'readonly' => true, 167 ), 168 'labels' => array( 169 'description' => __( 'Human-readable labels for the resource for various contexts.' ), 170 'type' => 'object', 171 'context' => array( 'edit' ), 172 'readonly' => true, 173 ), 174 'name' => array( 175 'description' => __( 'The title for the resource.' ), 176 'type' => 'string', 177 'context' => array( 'view', 'edit', 'embed' ), 178 'readonly' => true, 179 ), 180 'slug' => array( 181 'description' => __( 'An alphanumeric identifier for the resource.' ), 182 'type' => 'string', 183 'context' => array( 'view', 'edit', 'embed' ), 184 'readonly' => true, 185 ), 186 ), 187 ); 188 return $this->add_additional_fields_schema( $schema ); 189 } 190 191 /** 192 * Get the query params for collections 193 * 194 * @return array 195 */ 196 public function get_collection_params() { 197 return array( 198 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 199 ); 200 } 201 202 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 class WP_REST_Posts_Controller extends WP_REST_Controller { 4 5 /** 6 * Post type. 7 * 8 * @access protected 9 * @var string 10 */ 11 protected $post_type; 12 13 /** 14 * Instance of a post meta fields object. 15 * 16 * @access protected 17 * @var WP_REST_Post_Meta_Fields 18 */ 19 protected $meta; 20 21 /** 22 * Constructor. 23 * 24 * @param string $post_type Post type. 25 */ 26 public function __construct( $post_type ) { 27 $this->post_type = $post_type; 28 $this->namespace = 'wp/v2'; 29 $obj = get_post_type_object( $post_type ); 30 $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; 31 32 $this->meta = new WP_REST_Post_Meta_Fields( $this->post_type ); 33 } 34 35 /** 36 * Register the routes for the objects of the controller. 37 */ 38 public function register_routes() { 39 40 register_rest_route( $this->namespace, '/' . $this->rest_base, array( 41 array( 42 'methods' => WP_REST_Server::READABLE, 43 'callback' => array( $this, 'get_items' ), 44 'permission_callback' => array( $this, 'get_items_permissions_check' ), 45 'args' => $this->get_collection_params(), 46 ), 47 array( 48 'methods' => WP_REST_Server::CREATABLE, 49 'callback' => array( $this, 'create_item' ), 50 'permission_callback' => array( $this, 'create_item_permissions_check' ), 51 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 52 ), 53 'schema' => array( $this, 'get_public_item_schema' ), 54 ) ); 55 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 56 array( 57 'methods' => WP_REST_Server::READABLE, 58 'callback' => array( $this, 'get_item' ), 59 'permission_callback' => array( $this, 'get_item_permissions_check' ), 60 'args' => array( 61 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 62 'password' => array( 63 'description' => __( 'The password for the post if it is password protected.' ), 64 ), 65 ), 66 ), 67 array( 68 'methods' => WP_REST_Server::EDITABLE, 69 'callback' => array( $this, 'update_item' ), 70 'permission_callback' => array( $this, 'update_item_permissions_check' ), 71 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 72 ), 73 array( 74 'methods' => WP_REST_Server::DELETABLE, 75 'callback' => array( $this, 'delete_item' ), 76 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 77 'args' => array( 78 'force' => array( 79 'default' => false, 80 'description' => __( 'Whether to bypass trash and force deletion.' ), 81 ), 82 ), 83 ), 84 'schema' => array( $this, 'get_public_item_schema' ), 85 ) ); 86 } 87 88 /** 89 * Check if a given request has access to read /posts. 90 * 91 * @param WP_REST_Request $request Full details about the request. 92 * @return WP_Error|boolean 93 */ 94 public function get_items_permissions_check( $request ) { 95 96 $post_type = get_post_type_object( $this->post_type ); 97 98 if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) { 99 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit these posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); 100 } 101 102 return true; 103 } 104 105 /** 106 * Get a collection of posts. 107 * 108 * @param WP_REST_Request $request Full details about the request. 109 * @return WP_Error|WP_REST_Response 110 */ 111 public function get_items( $request ) { 112 113 // Make sure a search string is set in case the orderby is set to 'relevance'. 114 if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) && empty( $request['filter']['s'] ) ) { 115 return new WP_Error( 'rest_no_search_term_defined', __( 'You need to define a search term to order by relevance.' ), array( 'status' => 400 ) ); 116 } 117 118 // Retrieve the list of registered collection query parameters. 119 $registered = $this->get_collection_params(); 120 $args = array(); 121 122 // This array defines mappings between public API query parameters whose 123 // values are accepted as-passed, and their internal WP_Query parameter 124 // name equivalents (some are the same). Only values which are also 125 // present in $registered will be set. 126 $parameter_mappings = array( 127 'author' => 'author__in', 128 'author_exclude' => 'author__not_in', 129 'exclude' => 'post__not_in', 130 'include' => 'post__in', 131 'menu_order' => 'menu_order', 132 'offset' => 'offset', 133 'order' => 'order', 134 'orderby' => 'orderby', 135 'page' => 'paged', 136 'parent' => 'post_parent__in', 137 'parent_exclude' => 'post_parent__not_in', 138 'search' => 's', 139 'slug' => 'name', 140 'status' => 'post_status', 141 ); 142 143 // For each known parameter which is both registered and present in the request, 144 // set the parameter's value on the query $args. 145 foreach ( $parameter_mappings as $api_param => $wp_param ) { 146 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { 147 $args[ $wp_param ] = $request[ $api_param ]; 148 } 149 } 150 151 // Check for & assign any parameters which require special handling or setting. 152 153 $args['date_query'] = array(); 154 // Set before into date query. Date query must be specified as an array of an array. 155 if ( isset( $registered['before'], $request['before'] ) ) { 156 $args['date_query'][0]['before'] = $request['before']; 157 } 158 159 // Set after into date query. Date query must be specified as an array of an array. 160 if ( isset( $registered['after'], $request['after'] ) ) { 161 $args['date_query'][0]['after'] = $request['after']; 162 } 163 164 if ( isset( $registered['filter'] ) && is_array( $request['filter'] ) ) { 165 $args = array_merge( $args, $request['filter'] ); 166 unset( $args['filter'] ); 167 } 168 169 // Ensure our per_page parameter overrides any provided posts_per_page filter. 170 if ( isset( $registered['per_page'] ) ) { 171 $args['posts_per_page'] = $request['per_page']; 172 } 173 174 if ( isset( $registered['sticky'], $request['sticky'] ) ) { 175 $sticky_posts = get_option( 'sticky_posts', array() ); 176 if ( $sticky_posts && $request['sticky'] ) { 177 // As post__in will be used to only get sticky posts, 178 // we have to support the case where post__in was already 179 // specified. 180 $args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts; 181 182 // If we intersected, but there are no post ids in common, 183 // WP_Query won't return "no posts" for `post__in = array()` 184 // so we have to fake it a bit. 185 if ( ! $args['post__in'] ) { 186 $args['post__in'] = array( -1 ); 187 } 188 } elseif ( $sticky_posts ) { 189 // As post___not_in will be used to only get posts that 190 // are not sticky, we have to support the case where post__not_in 191 // was already specified. 192 $args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts ); 193 } 194 } 195 196 // Force the post_type argument, since it's not a user input variable. 197 $args['post_type'] = $this->post_type; 198 199 /** 200 * Filter the query arguments for a request. 201 * 202 * Enables adding extra arguments or setting defaults for a post 203 * collection request. 204 * 205 * @see https://developer.wordpress.org/reference/classes/wp_query/ 206 * 207 * @param array $args Key value array of query var to query value. 208 * @param WP_REST_Request $request The request used. 209 */ 210 $args = apply_filters( "rest_{$this->post_type}_query", $args, $request ); 211 $query_args = $this->prepare_items_query( $args, $request ); 212 213 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 214 foreach ( $taxonomies as $taxonomy ) { 215 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 216 $tax_exclude = $base . '_exclude'; 217 218 if ( ! empty( $request[ $base ] ) ) { 219 $query_args['tax_query'][] = array( 220 'taxonomy' => $taxonomy->name, 221 'field' => 'term_id', 222 'terms' => $request[ $base ], 223 'include_children' => false, 224 ); 225 } 226 227 if ( ! empty( $request[ $tax_exclude ] ) ) { 228 $query_args['tax_query'][] = array( 229 'taxonomy' => $taxonomy->name, 230 'field' => 'term_id', 231 'terms' => $request[ $tax_exclude ], 232 'include_children' => false, 233 'operator' => 'NOT IN', 234 ); 235 } 236 } 237 238 $posts_query = new WP_Query(); 239 $query_result = $posts_query->query( $query_args ); 240 241 // Allow access to all password protected posts if the context is edit. 242 if ( 'edit' === $request['context'] ) { 243 add_filter( 'post_password_required', '__return_false' ); 244 } 245 246 $posts = array(); 247 foreach ( $query_result as $post ) { 248 if ( ! $this->check_read_permission( $post ) ) { 249 continue; 250 } 251 252 $data = $this->prepare_item_for_response( $post, $request ); 253 $posts[] = $this->prepare_response_for_collection( $data ); 254 } 255 256 // Reset filter. 257 if ( 'edit' === $request['context'] ) { 258 remove_filter( 'post_password_required', '__return_false' ); 259 } 260 261 $page = (int) $query_args['paged']; 262 $total_posts = $posts_query->found_posts; 263 264 if ( $total_posts < 1 ) { 265 // Out-of-bounds, run the query again without LIMIT for total count. 266 unset( $query_args['paged'] ); 267 $count_query = new WP_Query(); 268 $count_query->query( $query_args ); 269 $total_posts = $count_query->found_posts; 270 } 271 272 $max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] ); 273 274 $response = rest_ensure_response( $posts ); 275 $response->header( 'X-WP-Total', (int) $total_posts ); 276 $response->header( 'X-WP-TotalPages', (int) $max_pages ); 277 278 $request_params = $request->get_query_params(); 279 if ( ! empty( $request_params['filter'] ) ) { 280 // Normalize the pagination params. 281 unset( $request_params['filter']['posts_per_page'], $request_params['filter']['paged'] ); 282 } 283 $base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); 284 285 if ( $page > 1 ) { 286 $prev_page = $page - 1; 287 if ( $prev_page > $max_pages ) { 288 $prev_page = $max_pages; 289 } 290 $prev_link = add_query_arg( 'page', $prev_page, $base ); 291 $response->link_header( 'prev', $prev_link ); 292 } 293 if ( $max_pages > $page ) { 294 $next_page = $page + 1; 295 $next_link = add_query_arg( 'page', $next_page, $base ); 296 $response->link_header( 'next', $next_link ); 297 } 298 299 return $response; 300 } 301 302 /** 303 * Check if a given request has access to read a post. 304 * 305 * @param WP_REST_Request $request Full details about the request. 306 * @return WP_Error|boolean 307 */ 308 public function get_item_permissions_check( $request ) { 309 310 $post = $this->get_post( (int) $request['id'] ); 311 312 if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) { 313 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => rest_authorization_required_code() ) ); 314 } 315 316 if ( $post && ! empty( $request['password'] ) ) { 317 // Check post password, and return error if invalid. 318 if ( ! hash_equals( $post->post_password, $request['password'] ) ) { 319 return new WP_Error( 'rest_post_incorrect_password', __( 'Incorrect post password.' ), array( 'status' => 403 ) ); 320 } 321 } 322 323 // Allow access to all password protected posts if the context is edit. 324 if ( 'edit' === $request['context'] ) { 325 add_filter( 'post_password_required', '__return_false' ); 326 } 327 328 if ( $post ) { 329 return $this->check_read_permission( $post ); 330 } 331 332 return true; 333 } 334 335 /** 336 * Can the user access password-protected content? 337 * 338 * This method determines whether we need to override the regular password 339 * check in core with a filter. 340 * 341 * @param WP_Post $post Post to check against. 342 * @param WP_REST_Request $request Request data to check. 343 * @return bool True if the user can access password-protected content, false otherwise. 344 */ 345 protected function can_access_password_content( $post, $request ) { 346 if ( empty( $post->post_password ) ) { 347 // No filter required. 348 return false; 349 } 350 351 // Edit context always gets access to password-protected posts. 352 if ( 'edit' === $request['context'] ) { 353 return true; 354 } 355 356 // No password, no auth. 357 if ( empty( $request['password'] ) ) { 358 return false; 359 } 360 361 // Double-check the request password. 362 return hash_equals( $post->post_password, $request['password'] ); 363 } 364 365 /** 366 * Get a single post. 367 * 368 * @param WP_REST_Request $request Full details about the request. 369 * @return WP_Error|WP_REST_Response 370 */ 371 public function get_item( $request ) { 372 $id = (int) $request['id']; 373 $post = $this->get_post( $id ); 374 375 if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { 376 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) ); 377 } 378 379 $data = $this->prepare_item_for_response( $post, $request ); 380 $response = rest_ensure_response( $data ); 381 382 if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) { 383 $response->link_header( 'alternate', get_permalink( $id ), array( 'type' => 'text/html' ) ); 384 } 385 386 return $response; 387 } 388 389 /** 390 * Check if a given request has access to create a post. 391 * 392 * @param WP_REST_Request $request Full details about the request. 393 * @return WP_Error|boolean 394 */ 395 public function create_item_permissions_check( $request ) { 396 397 $post_type = get_post_type_object( $this->post_type ); 398 399 if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 400 return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) ); 401 } 402 403 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 404 return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) ); 405 } 406 407 if ( ! current_user_can( $post_type->cap->create_posts ) ) { 408 return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create new posts.' ), array( 'status' => rest_authorization_required_code() ) ); 409 } 410 return true; 411 } 412 413 /** 414 * Create a single post. 415 * 416 * @param WP_REST_Request $request Full details about the request. 417 * @return WP_Error|WP_REST_Response 418 */ 419 public function create_item( $request ) { 420 if ( ! empty( $request['id'] ) ) { 421 return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) ); 422 } 423 424 $post = $this->prepare_item_for_database( $request ); 425 if ( is_wp_error( $post ) ) { 426 return $post; 427 } 428 429 $post->post_type = $this->post_type; 430 $post_id = wp_insert_post( $post, true ); 431 432 if ( is_wp_error( $post_id ) ) { 433 434 if ( 'db_insert_error' === $post_id->get_error_code() ) { 435 $post_id->add_data( array( 'status' => 500 ) ); 436 } else { 437 $post_id->add_data( array( 'status' => 400 ) ); 438 } 439 return $post_id; 440 } 441 $post->ID = $post_id; 442 443 $schema = $this->get_item_schema(); 444 445 if ( ! empty( $schema['properties']['sticky'] ) ) { 446 if ( ! empty( $request['sticky'] ) ) { 447 stick_post( $post_id ); 448 } else { 449 unstick_post( $post_id ); 450 } 451 } 452 453 if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { 454 $this->handle_featured_media( $request['featured_media'], $post->ID ); 455 } 456 457 if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) { 458 set_post_format( $post, $request['format'] ); 459 } 460 461 if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { 462 $this->handle_template( $request['template'], $post->ID ); 463 } 464 $terms_update = $this->handle_terms( $post->ID, $request ); 465 if ( is_wp_error( $terms_update ) ) { 466 return $terms_update; 467 } 468 469 $post = $this->get_post( $post_id ); 470 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 471 $meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] ); 472 if ( is_wp_error( $meta_update ) ) { 473 return $meta_update; 474 } 475 } 476 477 $fields_update = $this->update_additional_fields_for_object( $post, $request ); 478 if ( is_wp_error( $fields_update ) ) { 479 return $fields_update; 480 } 481 482 /** 483 * Fires after a single post is created or updated via the REST API. 484 * 485 * @param object $post Inserted Post object (not a WP_Post object). 486 * @param WP_REST_Request $request Request object. 487 * @param boolean $creating True when creating post, false when updating. 488 */ 489 do_action( "rest_insert_{$this->post_type}", $post, $request, true ); 490 491 $request->set_param( 'context', 'edit' ); 492 $response = $this->prepare_item_for_response( $post, $request ); 493 $response = rest_ensure_response( $response ); 494 $response->set_status( 201 ); 495 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) ); 496 497 return $response; 498 } 499 500 /** 501 * Check if a given request has access to update a post. 502 * 503 * @param WP_REST_Request $request Full details about the request. 504 * @return WP_Error|boolean 505 */ 506 public function update_item_permissions_check( $request ) { 507 508 $post = $this->get_post( $request['id'] ); 509 $post_type = get_post_type_object( $this->post_type ); 510 511 if ( $post && ! $this->check_update_permission( $post ) ) { 512 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to update this post.' ), array( 'status' => rest_authorization_required_code() ) ); 513 } 514 515 if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 516 return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to update posts as this user.' ), array( 'status' => rest_authorization_required_code() ) ); 517 } 518 519 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 520 return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) ); 521 } 522 523 return true; 524 } 525 526 /** 527 * Update a single post. 528 * 529 * @param WP_REST_Request $request Full details about the request. 530 * @return WP_Error|WP_REST_Response 531 */ 532 public function update_item( $request ) { 533 $id = (int) $request['id']; 534 $post = $this->get_post( $id ); 535 536 if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { 537 return new WP_Error( 'rest_post_invalid_id', __( 'Post id is invalid.' ), array( 'status' => 404 ) ); 538 } 539 540 $post = $this->prepare_item_for_database( $request ); 541 if ( is_wp_error( $post ) ) { 542 return $post; 543 } 544 // convert the post object to an array, otherwise wp_update_post will expect non-escaped input. 545 $post_id = wp_update_post( (array) $post, true ); 546 if ( is_wp_error( $post_id ) ) { 547 if ( 'db_update_error' === $post_id->get_error_code() ) { 548 $post_id->add_data( array( 'status' => 500 ) ); 549 } else { 550 $post_id->add_data( array( 'status' => 400 ) ); 551 } 552 return $post_id; 553 } 554 555 $schema = $this->get_item_schema(); 556 557 if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) { 558 set_post_format( $post, $request['format'] ); 559 } 560 561 if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { 562 $this->handle_featured_media( $request['featured_media'], $post_id ); 563 } 564 565 if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) { 566 if ( ! empty( $request['sticky'] ) ) { 567 stick_post( $post_id ); 568 } else { 569 unstick_post( $post_id ); 570 } 571 } 572 573 if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { 574 $this->handle_template( $request['template'], $post->ID ); 575 } 576 577 $terms_update = $this->handle_terms( $post->ID, $request ); 578 if ( is_wp_error( $terms_update ) ) { 579 return $terms_update; 580 } 581 582 $post = $this->get_post( $post_id ); 583 584 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 585 $meta_update = $this->meta->update_value( $request['meta'], $post->ID ); 586 if ( is_wp_error( $meta_update ) ) { 587 return $meta_update; 588 } 589 } 590 591 $fields_update = $this->update_additional_fields_for_object( $post, $request ); 592 if ( is_wp_error( $fields_update ) ) { 593 return $fields_update; 594 } 595 596 /* This action is documented in lib/endpoints/class-wp-rest-controller.php */ 597 do_action( "rest_insert_{$this->post_type}", $post, $request, false ); 598 599 $request->set_param( 'context', 'edit' ); 600 $response = $this->prepare_item_for_response( $post, $request ); 601 return rest_ensure_response( $response ); 602 } 603 604 /** 605 * Check if a given request has access to delete a post. 606 * 607 * @param WP_REST_Request $request Full details about the request. 608 * @return bool|WP_Error 609 */ 610 public function delete_item_permissions_check( $request ) { 611 612 $post = $this->get_post( $request['id'] ); 613 614 if ( $post && ! $this->check_delete_permission( $post ) ) { 615 return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete posts.' ), array( 'status' => rest_authorization_required_code() ) ); 616 } 617 618 return true; 619 } 620 621 /** 622 * Delete a single post. 623 * 624 * @param WP_REST_Request $request Full details about the request. 625 * @return WP_REST_Response|WP_Error 626 */ 627 public function delete_item( $request ) { 628 $id = (int) $request['id']; 629 $force = (bool) $request['force']; 630 631 $post = $this->get_post( $id ); 632 633 if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { 634 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) ); 635 } 636 637 $supports_trash = ( EMPTY_TRASH_DAYS > 0 ); 638 if ( 'attachment' === $post->post_type ) { 639 $supports_trash = $supports_trash && MEDIA_TRASH; 640 } 641 642 /** 643 * Filter whether a post is trashable. 644 * 645 * Return false to disable trash support for the post. 646 * 647 * @param boolean $supports_trash Whether the post type support trashing. 648 * @param WP_Post $post The Post object being considered for trashing support. 649 */ 650 $supports_trash = apply_filters( "rest_{$this->post_type}_trashable", $supports_trash, $post ); 651 652 if ( ! $this->check_delete_permission( $post ) ) { 653 return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) ); 654 } 655 656 $request->set_param( 'context', 'edit' ); 657 $response = $this->prepare_item_for_response( $post, $request ); 658 659 // If we're forcing, then delete permanently. 660 if ( $force ) { 661 $result = wp_delete_post( $id, true ); 662 } else { 663 // If we don't support trashing for this type, error out. 664 if ( ! $supports_trash ) { 665 return new WP_Error( 'rest_trash_not_supported', __( 'The post does not support trashing.' ), array( 'status' => 501 ) ); 666 } 667 668 // Otherwise, only trash if we haven't already. 669 if ( 'trash' === $post->post_status ) { 670 return new WP_Error( 'rest_already_trashed', __( 'The post has already been deleted.' ), array( 'status' => 410 ) ); 671 } 672 673 // (Note that internally this falls through to `wp_delete_post` if 674 // the trash is disabled.) 675 $result = wp_trash_post( $id ); 676 } 677 678 if ( ! $result ) { 679 return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); 680 } 681 682 /** 683 * Fires after a single post is deleted or trashed via the REST API. 684 * 685 * @param object $post The deleted or trashed post. 686 * @param WP_REST_Response $response The response data. 687 * @param WP_REST_Request $request The request sent to the API. 688 */ 689 do_action( "rest_delete_{$this->post_type}", $post, $response, $request ); 690 691 return $response; 692 } 693 694 /** 695 * Determine the allowed query_vars for a get_items() response and 696 * prepare for WP_Query. 697 * 698 * @param array $prepared_args Prepared WP_Query arguments. 699 * @param WP_REST_Request $request Full details about the request. 700 * @return array $query_args 701 */ 702 protected function prepare_items_query( $prepared_args = array(), $request = null ) { 703 704 $valid_vars = array_flip( $this->get_allowed_query_vars( $request ) ); 705 $query_args = array(); 706 foreach ( $valid_vars as $var => $index ) { 707 if ( isset( $prepared_args[ $var ] ) ) { 708 /** 709 * Filter the query_vars used in `get_items` for the constructed query. 710 * 711 * The dynamic portion of the hook name, $var, refers to the query_var key. 712 * 713 * @param mixed $prepared_args[ $var ] The query_var value. 714 */ 715 $query_args[ $var ] = apply_filters( "rest_query_var-{$var}", $prepared_args[ $var ] ); 716 } 717 } 718 719 if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) { 720 $query_args['ignore_sticky_posts'] = true; 721 } 722 723 if ( 'include' === $query_args['orderby'] ) { 724 $query_args['orderby'] = 'post__in'; 725 } 726 727 return $query_args; 728 } 729 730 /** 731 * Get all the WP Query vars that are allowed for the API request. 732 * 733 * @param WP_REST_Request $request Full details about the request. 734 * @return array 735 */ 736 protected function get_allowed_query_vars( $request = null ) { 737 global $wp; 738 739 /** 740 * Filter the publicly allowed query vars. 741 * 742 * Allows adjusting of the default query vars that are made public. 743 * 744 * @param array Array of allowed WP_Query query vars. 745 */ 746 $valid_vars = apply_filters( 'query_vars', $wp->public_query_vars ); 747 748 $post_type_obj = get_post_type_object( $this->post_type ); 749 if ( current_user_can( $post_type_obj->cap->edit_posts ) ) { 750 /** 751 * Filter the allowed 'private' query vars for authorized users. 752 * 753 * If the user has the `edit_posts` capability, we also allow use of 754 * private query parameters, which are only undesirable on the 755 * frontend, but are safe for use in query strings. 756 * 757 * To disable anyway, use 758 * `add_filter( 'rest_private_query_vars', '__return_empty_array' );` 759 * 760 * @param array $private_query_vars Array of allowed query vars for authorized users. 761 * } 762 */ 763 $private = apply_filters( 'rest_private_query_vars', $wp->private_query_vars ); 764 $valid_vars = array_merge( $valid_vars, $private ); 765 } 766 // Define our own in addition to WP's normal vars. 767 $rest_valid = array( 768 'author__in', 769 'author__not_in', 770 'ignore_sticky_posts', 771 'menu_order', 772 'offset', 773 'post__in', 774 'post__not_in', 775 'post_parent', 776 'post_parent__in', 777 'post_parent__not_in', 778 'posts_per_page', 779 'date_query', 780 ); 781 $valid_vars = array_merge( $valid_vars, $rest_valid ); 782 783 /** 784 * Filter allowed query vars for the REST API. 785 * 786 * This filter allows you to add or remove query vars from the final allowed 787 * list for all requests, including unauthenticated ones. To alter the 788 * vars for editors only, {@see rest_private_query_vars}. 789 * 790 * @param array { 791 * Array of allowed WP_Query query vars. 792 * 793 * @param string $allowed_query_var The query var to allow. 794 * @param WP_REST_Request $request Request object. 795 * } 796 */ 797 $valid_vars = apply_filters( 'rest_query_vars', $valid_vars, $request ); 798 799 return $valid_vars; 800 } 801 802 /** 803 * Check the post_date_gmt or modified_gmt and prepare any post or 804 * modified date for single post output. 805 * 806 * @param string $date_gmt GMT publication time. 807 * @param string|null $date Optional, default is null. Local publication time. 808 * @return string|null ISO8601/RFC3339 formatted datetime. 809 */ 810 protected function prepare_date_response( $date_gmt, $date = null ) { 811 // Use the date if passed. 812 if ( isset( $date ) ) { 813 return mysql_to_rfc3339( $date ); 814 } 815 816 // Return null if $date_gmt is empty/zeros. 817 if ( '0000-00-00 00:00:00' === $date_gmt ) { 818 return null; 819 } 820 821 // Return the formatted datetime. 822 return mysql_to_rfc3339( $date_gmt ); 823 } 824 825 /** 826 * Prepare a single post for create or update. 827 * 828 * @param WP_REST_Request $request Request object. 829 * @return WP_Error|stdClass $prepared_post Post object. 830 */ 831 protected function prepare_item_for_database( $request ) { 832 $prepared_post = new stdClass; 833 834 // ID. 835 if ( isset( $request['id'] ) ) { 836 $prepared_post->ID = absint( $request['id'] ); 837 } 838 839 $schema = $this->get_item_schema(); 840 841 // Post title. 842 if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) { 843 if ( is_string( $request['title'] ) ) { 844 $prepared_post->post_title = wp_filter_post_kses( $request['title'] ); 845 } elseif ( ! empty( $request['title']['raw'] ) ) { 846 $prepared_post->post_title = wp_filter_post_kses( $request['title']['raw'] ); 847 } 848 } 849 850 // Post content. 851 if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) { 852 if ( is_string( $request['content'] ) ) { 853 $prepared_post->post_content = wp_filter_post_kses( $request['content'] ); 854 } elseif ( isset( $request['content']['raw'] ) ) { 855 $prepared_post->post_content = wp_filter_post_kses( $request['content']['raw'] ); 856 } 857 } 858 859 // Post excerpt. 860 if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) { 861 if ( is_string( $request['excerpt'] ) ) { 862 $prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt'] ); 863 } elseif ( isset( $request['excerpt']['raw'] ) ) { 864 $prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt']['raw'] ); 865 } 866 } 867 868 // Post type. 869 if ( empty( $request['id'] ) ) { 870 // Creating new post, use default type for the controller. 871 $prepared_post->post_type = $this->post_type; 872 } else { 873 // Updating a post, use previous type. 874 $prepared_post->post_type = get_post_type( $request['id'] ); 875 } 876 $post_type = get_post_type_object( $prepared_post->post_type ); 877 878 // Post status. 879 if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) { 880 $status = $this->handle_status_param( $request['status'], $post_type ); 881 if ( is_wp_error( $status ) ) { 882 return $status; 883 } 884 885 $prepared_post->post_status = $status; 886 } 887 888 // Post date. 889 if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) { 890 $date_data = rest_get_date_with_gmt( $request['date'] ); 891 892 if ( ! empty( $date_data ) ) { 893 list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; 894 } 895 } elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) { 896 $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); 897 898 if ( ! empty( $date_data ) ) { 899 list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; 900 } 901 } 902 // Post slug. 903 if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) { 904 $prepared_post->post_name = $request['slug']; 905 } 906 907 // Author. 908 if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) { 909 $post_author = (int) $request['author']; 910 if ( get_current_user_id() !== $post_author ) { 911 $user_obj = get_userdata( $post_author ); 912 if ( ! $user_obj ) { 913 return new WP_Error( 'rest_invalid_author', __( 'Invalid author id.' ), array( 'status' => 400 ) ); 914 } 915 } 916 $prepared_post->post_author = $post_author; 917 } 918 919 // Post password. 920 if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) && '' !== $request['password'] ) { 921 $prepared_post->post_password = $request['password']; 922 923 if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { 924 return new WP_Error( 'rest_invalid_field', __( 'A post can not be sticky and have a password.' ), array( 'status' => 400 ) ); 925 } 926 927 if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) { 928 return new WP_Error( 'rest_invalid_field', __( 'A sticky post can not be password protected.' ), array( 'status' => 400 ) ); 929 } 930 } 931 932 if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { 933 if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) { 934 return new WP_Error( 'rest_invalid_field', __( 'A password protected post can not be set to sticky.' ), array( 'status' => 400 ) ); 935 } 936 } 937 938 // Parent. 939 if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) { 940 $parent = $this->get_post( (int) $request['parent'] ); 941 if ( empty( $parent ) ) { 942 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent id.' ), array( 'status' => 400 ) ); 943 } 944 945 $prepared_post->post_parent = (int) $parent->ID; 946 } 947 948 // Menu order. 949 if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) { 950 $prepared_post->menu_order = (int) $request['menu_order']; 951 } 952 953 // Comment status. 954 if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) { 955 $prepared_post->comment_status = $request['comment_status']; 956 } 957 958 // Ping status. 959 if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) { 960 $prepared_post->ping_status = $request['ping_status']; 961 } 962 /** 963 * Filter a post before it is inserted via the REST API. 964 * 965 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being 966 * prepared for insertion. 967 * 968 * @param stdClass $prepared_post An object representing a single post prepared 969 * for inserting or updating the database. 970 * @param WP_REST_Request $request Request object. 971 */ 972 return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request ); 973 974 } 975 976 /** 977 * Determine validity and normalize provided status param. 978 * 979 * @param string $post_status Post status. 980 * @param object $post_type Post type. 981 * @return WP_Error|string $post_status 982 */ 983 protected function handle_status_param( $post_status, $post_type ) { 984 985 switch ( $post_status ) { 986 case 'draft': 987 case 'pending': 988 break; 989 case 'private': 990 if ( ! current_user_can( $post_type->cap->publish_posts ) ) { 991 return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create private posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); 992 } 993 break; 994 case 'publish': 995 case 'future': 996 if ( ! current_user_can( $post_type->cap->publish_posts ) ) { 997 return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to publish posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); 998 } 999 break; 1000 default: 1001 if ( ! get_post_status_object( $post_status ) ) { 1002 $post_status = 'draft'; 1003 } 1004 break; 1005 } 1006 1007 return $post_status; 1008 } 1009 1010 /** 1011 * Determine the featured media based on a request param. 1012 * 1013 * @param int $featured_media Featured Media ID. 1014 * @param int $post_id Post ID. 1015 * @return bool|WP_Error 1016 */ 1017 protected function handle_featured_media( $featured_media, $post_id ) { 1018 1019 $featured_media = (int) $featured_media; 1020 if ( $featured_media ) { 1021 $result = set_post_thumbnail( $post_id, $featured_media ); 1022 if ( $result ) { 1023 return true; 1024 } else { 1025 return new WP_Error( 'rest_invalid_featured_media', __( 'Invalid featured media id.' ), array( 'status' => 400 ) ); 1026 } 1027 } else { 1028 return delete_post_thumbnail( $post_id ); 1029 } 1030 1031 } 1032 1033 /** 1034 * Set the template for a page. 1035 * 1036 * @param string $template Page template filename. 1037 * @param integer $post_id Post ID. 1038 */ 1039 public function handle_template( $template, $post_id ) { 1040 if ( in_array( $template, array_keys( wp_get_theme()->get_page_templates( $this->get_post( $post_id ) ) ), true ) ) { 1041 update_post_meta( $post_id, '_wp_page_template', $template ); 1042 } else { 1043 update_post_meta( $post_id, '_wp_page_template', '' ); 1044 } 1045 } 1046 1047 /** 1048 * Update the post's terms from a REST request. 1049 * 1050 * @param int $post_id The post ID to update the terms form. 1051 * @param WP_REST_Request $request The request object with post and terms data. 1052 * @return null|WP_Error WP_Error on an error assigning any of the terms. 1053 */ 1054 protected function handle_terms( $post_id, $request ) { 1055 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 1056 foreach ( $taxonomies as $taxonomy ) { 1057 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1058 1059 if ( ! isset( $request[ $base ] ) ) { 1060 continue; 1061 } 1062 $terms = array_map( 'absint', $request[ $base ] ); 1063 $result = wp_set_object_terms( $post_id, $terms, $taxonomy->name ); 1064 if ( is_wp_error( $result ) ) { 1065 return $result; 1066 } 1067 } 1068 } 1069 1070 /** 1071 * Check if a given post type should be viewed or managed. 1072 * 1073 * @param object|string $post_type Post type name or object. 1074 * @return boolean Is post type allowed? 1075 */ 1076 protected function check_is_post_type_allowed( $post_type ) { 1077 if ( ! is_object( $post_type ) ) { 1078 $post_type = get_post_type_object( $post_type ); 1079 } 1080 1081 if ( ! empty( $post_type ) && ! empty( $post_type->show_in_rest ) ) { 1082 return true; 1083 } 1084 1085 return false; 1086 } 1087 1088 /** 1089 * Check if we can read a post. 1090 * 1091 * Correctly handles posts with the inherit status. 1092 * 1093 * @param object $post Post object. 1094 * @return boolean Can we read it? 1095 */ 1096 public function check_read_permission( $post ) { 1097 $post_type = get_post_type_object( $post->post_type ); 1098 if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 1099 return false; 1100 } 1101 1102 // Can we read the post? 1103 if ( 'publish' === $post->post_status || current_user_can( $post_type->cap->read_post, $post->ID ) ) { 1104 return true; 1105 } 1106 1107 $post_status_obj = get_post_status_object( $post->post_status ); 1108 if ( $post_status_obj && $post_status_obj->public ) { 1109 return true; 1110 } 1111 1112 // Can we read the parent if we're inheriting? 1113 if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) { 1114 $parent = $this->get_post( $post->post_parent ); 1115 return $this->check_read_permission( $parent ); 1116 } 1117 1118 // If we don't have a parent, but the status is set to inherit, assume 1119 // it's published (as per get_post_status()). 1120 if ( 'inherit' === $post->post_status ) { 1121 return true; 1122 } 1123 1124 return false; 1125 } 1126 1127 /** 1128 * Check if we can edit a post. 1129 * 1130 * @param object $post Post object. 1131 * @return boolean Can we edit it? 1132 */ 1133 protected function check_update_permission( $post ) { 1134 $post_type = get_post_type_object( $post->post_type ); 1135 1136 if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 1137 return false; 1138 } 1139 1140 return current_user_can( $post_type->cap->edit_post, $post->ID ); 1141 } 1142 1143 /** 1144 * Check if we can create a post. 1145 * 1146 * @param object $post Post object. 1147 * @return boolean Can we create it?. 1148 */ 1149 protected function check_create_permission( $post ) { 1150 $post_type = get_post_type_object( $post->post_type ); 1151 1152 if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 1153 return false; 1154 } 1155 1156 return current_user_can( $post_type->cap->create_posts ); 1157 } 1158 1159 /** 1160 * Check if we can delete a post. 1161 * 1162 * @param object $post Post object. 1163 * @return boolean Can we delete it? 1164 */ 1165 protected function check_delete_permission( $post ) { 1166 $post_type = get_post_type_object( $post->post_type ); 1167 1168 if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 1169 return false; 1170 } 1171 1172 return current_user_can( $post_type->cap->delete_post, $post->ID ); 1173 } 1174 1175 /** 1176 * Prepare a single post output for response. 1177 * 1178 * @param WP_Post $post Post object. 1179 * @param WP_REST_Request $request Request object. 1180 * @return WP_REST_Response $data 1181 */ 1182 public function prepare_item_for_response( $post, $request ) { 1183 $GLOBALS['post'] = $post; 1184 setup_postdata( $post ); 1185 1186 $schema = $this->get_item_schema(); 1187 1188 // Base fields for every post. 1189 $data = array(); 1190 1191 if ( ! empty( $schema['properties']['id'] ) ) { 1192 $data['id'] = $post->ID; 1193 } 1194 1195 if ( ! empty( $schema['properties']['date'] ) ) { 1196 $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); 1197 } 1198 1199 if ( ! empty( $schema['properties']['date_gmt'] ) ) { 1200 $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); 1201 } 1202 1203 if ( ! empty( $schema['properties']['guid'] ) ) { 1204 $data['guid'] = array( 1205 /** This filter is documented in wp-includes/post-template.php */ 1206 'rendered' => apply_filters( 'get_the_guid', $post->guid ), 1207 'raw' => $post->guid, 1208 ); 1209 } 1210 1211 if ( ! empty( $schema['properties']['modified'] ) ) { 1212 $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); 1213 } 1214 1215 if ( ! empty( $schema['properties']['modified_gmt'] ) ) { 1216 $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); 1217 } 1218 1219 if ( ! empty( $schema['properties']['password'] ) ) { 1220 $data['password'] = $post->post_password; 1221 } 1222 1223 if ( ! empty( $schema['properties']['slug'] ) ) { 1224 $data['slug'] = $post->post_name; 1225 } 1226 1227 if ( ! empty( $schema['properties']['status'] ) ) { 1228 $data['status'] = $post->post_status; 1229 } 1230 1231 if ( ! empty( $schema['properties']['type'] ) ) { 1232 $data['type'] = $post->post_type; 1233 } 1234 1235 if ( ! empty( $schema['properties']['link'] ) ) { 1236 $data['link'] = get_permalink( $post->ID ); 1237 } 1238 1239 if ( ! empty( $schema['properties']['title'] ) ) { 1240 add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 1241 $data['title'] = array( 1242 'raw' => $post->post_title, 1243 'rendered' => get_the_title( $post->ID ), 1244 ); 1245 remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 1246 } 1247 1248 $has_password_filter = false; 1249 if ( $this->can_access_password_content( $post, $request ) ) { 1250 // Allow access to the post, permissions already checked before. 1251 add_filter( 'post_password_required', '__return_false' ); 1252 $has_password_filter = true; 1253 } 1254 1255 if ( ! empty( $schema['properties']['content'] ) ) { 1256 $data['content'] = array( 1257 'raw' => $post->post_content, 1258 /** This filter is documented in wp-includes/post-template.php */ 1259 'rendered' => post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ), 1260 'protected' => (bool) $post->post_password, 1261 ); 1262 } 1263 1264 if ( ! empty( $schema['properties']['excerpt'] ) ) { 1265 /** This filter is documented in wp-includes/post-template.php */ 1266 $excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) ); 1267 $data['excerpt'] = array( 1268 'raw' => $post->post_excerpt, 1269 'rendered' => post_password_required( $post ) ? '' : $excerpt, 1270 'protected' => (bool) $post->post_password, 1271 ); 1272 } 1273 1274 if ( $has_password_filter ) { 1275 // Reset filter. 1276 remove_filter( 'post_password_required', '__return_false' ); 1277 } 1278 1279 if ( ! empty( $schema['properties']['author'] ) ) { 1280 $data['author'] = (int) $post->post_author; 1281 } 1282 1283 if ( ! empty( $schema['properties']['featured_media'] ) ) { 1284 $data['featured_media'] = (int) get_post_thumbnail_id( $post->ID ); 1285 } 1286 1287 if ( ! empty( $schema['properties']['parent'] ) ) { 1288 $data['parent'] = (int) $post->post_parent; 1289 } 1290 1291 if ( ! empty( $schema['properties']['menu_order'] ) ) { 1292 $data['menu_order'] = (int) $post->menu_order; 1293 } 1294 1295 if ( ! empty( $schema['properties']['comment_status'] ) ) { 1296 $data['comment_status'] = $post->comment_status; 1297 } 1298 1299 if ( ! empty( $schema['properties']['ping_status'] ) ) { 1300 $data['ping_status'] = $post->ping_status; 1301 } 1302 1303 if ( ! empty( $schema['properties']['sticky'] ) ) { 1304 $data['sticky'] = is_sticky( $post->ID ); 1305 } 1306 1307 if ( ! empty( $schema['properties']['template'] ) ) { 1308 if ( $template = get_page_template_slug( $post->ID ) ) { 1309 $data['template'] = $template; 1310 } else { 1311 $data['template'] = ''; 1312 } 1313 } 1314 1315 if ( ! empty( $schema['properties']['format'] ) ) { 1316 $data['format'] = get_post_format( $post->ID ); 1317 // Fill in blank post format. 1318 if ( empty( $data['format'] ) ) { 1319 $data['format'] = 'standard'; 1320 } 1321 } 1322 1323 if ( ! empty( $schema['properties']['meta'] ) ) { 1324 $data['meta'] = $this->meta->get_value( $post->ID, $request ); 1325 } 1326 1327 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 1328 foreach ( $taxonomies as $taxonomy ) { 1329 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1330 if ( ! empty( $schema['properties'][ $base ] ) ) { 1331 $terms = get_the_terms( $post, $taxonomy->name ); 1332 $data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array(); 1333 } 1334 } 1335 1336 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 1337 $data = $this->add_additional_fields_to_object( $data, $request ); 1338 $data = $this->filter_response_by_context( $data, $context ); 1339 1340 // Wrap the data in a response object. 1341 $response = rest_ensure_response( $data ); 1342 1343 $response->add_links( $this->prepare_links( $post ) ); 1344 1345 /** 1346 * Filter the post data for a response. 1347 * 1348 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being 1349 * prepared for the response. 1350 * 1351 * @param WP_REST_Response $response The response object. 1352 * @param WP_Post $post Post object. 1353 * @param WP_REST_Request $request Request object. 1354 */ 1355 return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request ); 1356 } 1357 1358 /** 1359 * Overwrite the default protected title format. 1360 * 1361 * By default WordPress will show password protected posts with a title of 1362 * "Protected: %s", as the REST API communicates the protected status of a post 1363 * in a machine readable format, we remove the "Protected: " prefix. 1364 * 1365 * @return string 1366 */ 1367 public function protected_title_format() { 1368 return '%s'; 1369 } 1370 1371 /** 1372 * Prepare links for the request. 1373 * 1374 * @param WP_Post $post Post object. 1375 * @return array Links for the given post. 1376 */ 1377 protected function prepare_links( $post ) { 1378 $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); 1379 1380 // Entity meta. 1381 $links = array( 1382 'self' => array( 1383 'href' => rest_url( trailingslashit( $base ) . $post->ID ), 1384 ), 1385 'collection' => array( 1386 'href' => rest_url( $base ), 1387 ), 1388 'about' => array( 1389 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), 1390 ), 1391 ); 1392 1393 if ( ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'author' ) ) 1394 && ! empty( $post->post_author ) ) { 1395 $links['author'] = array( 1396 'href' => rest_url( 'wp/v2/users/' . $post->post_author ), 1397 'embeddable' => true, 1398 ); 1399 } 1400 1401 if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'comments' ) ) { 1402 $replies_url = rest_url( 'wp/v2/comments' ); 1403 $replies_url = add_query_arg( 'post', $post->ID, $replies_url ); 1404 $links['replies'] = array( 1405 'href' => $replies_url, 1406 'embeddable' => true, 1407 ); 1408 } 1409 1410 if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'revisions' ) ) { 1411 $links['version-history'] = array( 1412 'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions' ), 1413 ); 1414 } 1415 $post_type_obj = get_post_type_object( $post->post_type ); 1416 if ( $post_type_obj->hierarchical && ! empty( $post->post_parent ) ) { 1417 $links['up'] = array( 1418 'href' => rest_url( trailingslashit( $base ) . (int) $post->post_parent ), 1419 'embeddable' => true, 1420 ); 1421 } 1422 1423 // If we have a featured media, add that. 1424 if ( $featured_media = get_post_thumbnail_id( $post->ID ) ) { 1425 $image_url = rest_url( 'wp/v2/media/' . $featured_media ); 1426 $links['https://api.w.org/featuredmedia'] = array( 1427 'href' => $image_url, 1428 'embeddable' => true, 1429 ); 1430 } 1431 if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ), true ) ) { 1432 $attachments_url = rest_url( 'wp/v2/media' ); 1433 $attachments_url = add_query_arg( 'parent', $post->ID, $attachments_url ); 1434 $links['https://api.w.org/attachment'] = array( 1435 'href' => $attachments_url, 1436 ); 1437 } 1438 1439 $taxonomies = get_object_taxonomies( $post->post_type ); 1440 if ( ! empty( $taxonomies ) ) { 1441 $links['https://api.w.org/term'] = array(); 1442 1443 foreach ( $taxonomies as $tax ) { 1444 $taxonomy_obj = get_taxonomy( $tax ); 1445 // Skip taxonomies that are not public. 1446 if ( empty( $taxonomy_obj->show_in_rest ) ) { 1447 continue; 1448 } 1449 1450 $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax; 1451 $terms_url = add_query_arg( 1452 'post', 1453 $post->ID, 1454 rest_url( 'wp/v2/' . $tax_base ) 1455 ); 1456 1457 $links['https://api.w.org/term'][] = array( 1458 'href' => $terms_url, 1459 'taxonomy' => $tax, 1460 'embeddable' => true, 1461 ); 1462 } 1463 } 1464 1465 return $links; 1466 } 1467 1468 /** 1469 * Get the Post's schema, conforming to JSON Schema. 1470 * 1471 * @return array 1472 */ 1473 public function get_item_schema() { 1474 1475 $schema = array( 1476 '$schema' => 'http://json-schema.org/draft-04/schema#', 1477 'title' => $this->post_type, 1478 'type' => 'object', 1479 /* 1480 * Base properties for every Post. 1481 */ 1482 'properties' => array( 1483 'date' => array( 1484 'description' => __( "The date the object was published, in the site's timezone." ), 1485 'type' => 'string', 1486 'format' => 'date-time', 1487 'context' => array( 'view', 'edit', 'embed' ), 1488 ), 1489 'date_gmt' => array( 1490 'description' => __( 'The date the object was published, as GMT.' ), 1491 'type' => 'string', 1492 'format' => 'date-time', 1493 'context' => array( 'view', 'edit' ), 1494 ), 1495 'guid' => array( 1496 'description' => __( 'The globally unique identifier for the object.' ), 1497 'type' => 'object', 1498 'context' => array( 'view', 'edit' ), 1499 'readonly' => true, 1500 'properties' => array( 1501 'raw' => array( 1502 'description' => __( 'GUID for the object, as it exists in the database.' ), 1503 'type' => 'string', 1504 'context' => array( 'edit' ), 1505 'readonly' => true, 1506 ), 1507 'rendered' => array( 1508 'description' => __( 'GUID for the object, transformed for display.' ), 1509 'type' => 'string', 1510 'context' => array( 'view', 'edit' ), 1511 'readonly' => true, 1512 ), 1513 ), 1514 ), 1515 'id' => array( 1516 'description' => __( 'Unique identifier for the object.' ), 1517 'type' => 'integer', 1518 'context' => array( 'view', 'edit', 'embed' ), 1519 'readonly' => true, 1520 ), 1521 'link' => array( 1522 'description' => __( 'URL to the object.' ), 1523 'type' => 'string', 1524 'format' => 'uri', 1525 'context' => array( 'view', 'edit', 'embed' ), 1526 'readonly' => true, 1527 ), 1528 'modified' => array( 1529 'description' => __( "The date the object was last modified, in the site's timezone." ), 1530 'type' => 'string', 1531 'format' => 'date-time', 1532 'context' => array( 'view', 'edit' ), 1533 'readonly' => true, 1534 ), 1535 'modified_gmt' => array( 1536 'description' => __( 'The date the object was last modified, as GMT.' ), 1537 'type' => 'string', 1538 'format' => 'date-time', 1539 'context' => array( 'view', 'edit' ), 1540 'readonly' => true, 1541 ), 1542 'slug' => array( 1543 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), 1544 'type' => 'string', 1545 'context' => array( 'view', 'edit', 'embed' ), 1546 'arg_options' => array( 1547 'sanitize_callback' => array( $this, 'sanitize_slug' ), 1548 ), 1549 ), 1550 'status' => array( 1551 'description' => __( 'A named status for the object.' ), 1552 'type' => 'string', 1553 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 1554 'context' => array( 'edit' ), 1555 ), 1556 'type' => array( 1557 'description' => __( 'Type of Post for the object.' ), 1558 'type' => 'string', 1559 'context' => array( 'view', 'edit', 'embed' ), 1560 'readonly' => true, 1561 ), 1562 ), 1563 ); 1564 1565 $post_type_obj = get_post_type_object( $this->post_type ); 1566 if ( $post_type_obj->hierarchical ) { 1567 $schema['properties']['parent'] = array( 1568 'description' => __( 'The id for the parent of the object.' ), 1569 'type' => 'integer', 1570 'context' => array( 'view', 'edit' ), 1571 ); 1572 } 1573 1574 $post_type_attributes = array( 1575 'title', 1576 'editor', 1577 'author', 1578 'excerpt', 1579 'thumbnail', 1580 'comments', 1581 'revisions', 1582 'page-attributes', 1583 'post-formats', 1584 'custom-fields', 1585 ); 1586 $fixed_schemas = array( 1587 'post' => array( 1588 'title', 1589 'editor', 1590 'author', 1591 'excerpt', 1592 'thumbnail', 1593 'comments', 1594 'revisions', 1595 'post-formats', 1596 'custom-fields', 1597 ), 1598 'page' => array( 1599 'title', 1600 'editor', 1601 'author', 1602 'excerpt', 1603 'thumbnail', 1604 'comments', 1605 'revisions', 1606 'page-attributes', 1607 'custom-fields', 1608 ), 1609 'attachment' => array( 1610 'title', 1611 'author', 1612 'comments', 1613 'revisions', 1614 'custom-fields', 1615 ), 1616 ); 1617 foreach ( $post_type_attributes as $attribute ) { 1618 if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ], true ) ) { 1619 continue; 1620 } elseif ( ! isset( $fixed_schemas[ $this->post_type ] ) && ! post_type_supports( $this->post_type, $attribute ) ) { 1621 continue; 1622 } 1623 1624 switch ( $attribute ) { 1625 1626 case 'title': 1627 $schema['properties']['title'] = array( 1628 'description' => __( 'The title for the object.' ), 1629 'type' => 'object', 1630 'context' => array( 'view', 'edit', 'embed' ), 1631 'properties' => array( 1632 'raw' => array( 1633 'description' => __( 'Title for the object, as it exists in the database.' ), 1634 'type' => 'string', 1635 'context' => array( 'edit' ), 1636 ), 1637 'rendered' => array( 1638 'description' => __( 'HTML title for the object, transformed for display.' ), 1639 'type' => 'string', 1640 'context' => array( 'view', 'edit', 'embed' ), 1641 'readonly' => true, 1642 ), 1643 ), 1644 ); 1645 break; 1646 1647 case 'editor': 1648 $schema['properties']['content'] = array( 1649 'description' => __( 'The content for the object.' ), 1650 'type' => 'object', 1651 'context' => array( 'view', 'edit' ), 1652 'properties' => array( 1653 'raw' => array( 1654 'description' => __( 'Content for the object, as it exists in the database.' ), 1655 'type' => 'string', 1656 'context' => array( 'edit' ), 1657 ), 1658 'rendered' => array( 1659 'description' => __( 'HTML content for the object, transformed for display.' ), 1660 'type' => 'string', 1661 'context' => array( 'view', 'edit' ), 1662 'readonly' => true, 1663 ), 1664 'protected' => array( 1665 'description' => __( 'Whether the content is protected with a password.' ), 1666 'type' => 'boolean', 1667 'context' => array( 'view', 'edit', 'embed' ), 1668 'readonly' => true, 1669 ), 1670 ), 1671 ); 1672 break; 1673 1674 case 'author': 1675 $schema['properties']['author'] = array( 1676 'description' => __( 'The id for the author of the object.' ), 1677 'type' => 'integer', 1678 'context' => array( 'view', 'edit', 'embed' ), 1679 ); 1680 break; 1681 1682 case 'excerpt': 1683 $schema['properties']['excerpt'] = array( 1684 'description' => __( 'The excerpt for the object.' ), 1685 'type' => 'object', 1686 'context' => array( 'view', 'edit', 'embed' ), 1687 'properties' => array( 1688 'raw' => array( 1689 'description' => __( 'Excerpt for the object, as it exists in the database.' ), 1690 'type' => 'string', 1691 'context' => array( 'edit' ), 1692 ), 1693 'rendered' => array( 1694 'description' => __( 'HTML excerpt for the object, transformed for display.' ), 1695 'type' => 'string', 1696 'context' => array( 'view', 'edit', 'embed' ), 1697 'readonly' => true, 1698 ), 1699 'protected' => array( 1700 'description' => __( 'Whether the excerpt is protected with a password.' ), 1701 'type' => 'boolean', 1702 'context' => array( 'view', 'edit', 'embed' ), 1703 'readonly' => true, 1704 ), 1705 ), 1706 ); 1707 break; 1708 1709 case 'thumbnail': 1710 $schema['properties']['featured_media'] = array( 1711 'description' => __( 'The id of the featured media for the object.' ), 1712 'type' => 'integer', 1713 'context' => array( 'view', 'edit' ), 1714 ); 1715 break; 1716 1717 case 'comments': 1718 $schema['properties']['comment_status'] = array( 1719 'description' => __( 'Whether or not comments are open on the object.' ), 1720 'type' => 'string', 1721 'enum' => array( 'open', 'closed' ), 1722 'context' => array( 'view', 'edit' ), 1723 ); 1724 $schema['properties']['ping_status'] = array( 1725 'description' => __( 'Whether or not the object can be pinged.' ), 1726 'type' => 'string', 1727 'enum' => array( 'open', 'closed' ), 1728 'context' => array( 'view', 'edit' ), 1729 ); 1730 break; 1731 1732 case 'page-attributes': 1733 $schema['properties']['menu_order'] = array( 1734 'description' => __( 'The order of the object in relation to other object of its type.' ), 1735 'type' => 'integer', 1736 'context' => array( 'view', 'edit' ), 1737 ); 1738 break; 1739 1740 case 'post-formats': 1741 $schema['properties']['format'] = array( 1742 'description' => __( 'The format for the object.' ), 1743 'type' => 'string', 1744 'enum' => array_values( get_post_format_slugs() ), 1745 'context' => array( 'view', 'edit' ), 1746 ); 1747 break; 1748 1749 case 'custom-fields': 1750 $schema['properties']['meta'] = $this->meta->get_field_schema(); 1751 break; 1752 1753 } 1754 } 1755 1756 if ( 'post' === $this->post_type ) { 1757 $schema['properties']['sticky'] = array( 1758 'description' => __( 'Whether or not the object should be treated as sticky.' ), 1759 'type' => 'boolean', 1760 'context' => array( 'view', 'edit' ), 1761 ); 1762 1763 $schema['properties']['password'] = array( 1764 'description' => __( 'A password to protect access to the content and excerpt.' ), 1765 'type' => 'string', 1766 'context' => array( 'edit' ), 1767 ); 1768 } 1769 1770 if ( 'page' === $this->post_type ) { 1771 $schema['properties']['template'] = array( 1772 'description' => __( 'The theme file to use to display the object.' ), 1773 'type' => 'string', 1774 'enum' => array_keys( wp_get_theme()->get_page_templates() ), 1775 'context' => array( 'view', 'edit' ), 1776 ); 1777 } 1778 1779 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 1780 foreach ( $taxonomies as $taxonomy ) { 1781 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1782 $schema['properties'][ $base ] = array( 1783 'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ), 1784 'type' => 'array', 1785 'context' => array( 'view', 'edit' ), 1786 ); 1787 $schema['properties'][ $base . '_exclude' ] = array( 1788 'description' => sprintf( __( 'The terms in the %s taxonomy that should not be assigned to the object.' ), $taxonomy->name ), 1789 'type' => 'array', 1790 'context' => array( 'view', 'edit' ), 1791 ); 1792 } 1793 1794 return $this->add_additional_fields_schema( $schema ); 1795 } 1796 1797 /** 1798 * Get the query params for collections of attachments. 1799 * 1800 * @return array 1801 */ 1802 public function get_collection_params() { 1803 $params = parent::get_collection_params(); 1804 1805 $params['context']['default'] = 'view'; 1806 1807 $params['after'] = array( 1808 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.' ), 1809 'type' => 'string', 1810 'format' => 'date-time', 1811 'validate_callback' => 'rest_validate_request_arg', 1812 ); 1813 if ( post_type_supports( $this->post_type, 'author' ) ) { 1814 $params['author'] = array( 1815 'description' => __( 'Limit result set to posts assigned to specific authors.' ), 1816 'type' => 'array', 1817 'default' => array(), 1818 'sanitize_callback' => 'wp_parse_id_list', 1819 ); 1820 $params['author_exclude'] = array( 1821 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ), 1822 'type' => 'array', 1823 'default' => array(), 1824 'sanitize_callback' => 'wp_parse_id_list', 1825 ); 1826 } 1827 $params['before'] = array( 1828 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.' ), 1829 'type' => 'string', 1830 'format' => 'date-time', 1831 'validate_callback' => 'rest_validate_request_arg', 1832 ); 1833 $params['exclude'] = array( 1834 'description' => __( 'Ensure result set excludes specific ids.' ), 1835 'type' => 'array', 1836 'default' => array(), 1837 'sanitize_callback' => 'wp_parse_id_list', 1838 ); 1839 $params['include'] = array( 1840 'description' => __( 'Limit result set to specific ids.' ), 1841 'type' => 'array', 1842 'default' => array(), 1843 'sanitize_callback' => 'wp_parse_id_list', 1844 ); 1845 if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { 1846 $params['menu_order'] = array( 1847 'description' => __( 'Limit result set to resources with a specific menu_order value.' ), 1848 'type' => 'integer', 1849 'sanitize_callback' => 'absint', 1850 'validate_callback' => 'rest_validate_request_arg', 1851 ); 1852 } 1853 $params['offset'] = array( 1854 'description' => __( 'Offset the result set by a specific number of items.' ), 1855 'type' => 'integer', 1856 'sanitize_callback' => 'absint', 1857 'validate_callback' => 'rest_validate_request_arg', 1858 ); 1859 $params['order'] = array( 1860 'description' => __( 'Order sort attribute ascending or descending.' ), 1861 'type' => 'string', 1862 'default' => 'desc', 1863 'enum' => array( 'asc', 'desc' ), 1864 'validate_callback' => 'rest_validate_request_arg', 1865 ); 1866 $params['orderby'] = array( 1867 'description' => __( 'Sort collection by object attribute.' ), 1868 'type' => 'string', 1869 'default' => 'date', 1870 'enum' => array( 1871 'date', 1872 'relevance', 1873 'id', 1874 'include', 1875 'title', 1876 'slug', 1877 ), 1878 'validate_callback' => 'rest_validate_request_arg', 1879 ); 1880 if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { 1881 $params['orderby']['enum'][] = 'menu_order'; 1882 } 1883 1884 $post_type_obj = get_post_type_object( $this->post_type ); 1885 if ( $post_type_obj->hierarchical || 'attachment' === $this->post_type ) { 1886 $params['parent'] = array( 1887 'description' => __( 'Limit result set to those of particular parent ids.' ), 1888 'type' => 'array', 1889 'sanitize_callback' => 'wp_parse_id_list', 1890 'default' => array(), 1891 ); 1892 $params['parent_exclude'] = array( 1893 'description' => __( 'Limit result set to all items except those of a particular parent id.' ), 1894 'type' => 'array', 1895 'sanitize_callback' => 'wp_parse_id_list', 1896 'default' => array(), 1897 ); 1898 } 1899 1900 $params['slug'] = array( 1901 'description' => __( 'Limit result set to posts with a specific slug.' ), 1902 'type' => 'string', 1903 'validate_callback' => 'rest_validate_request_arg', 1904 ); 1905 $params['status'] = array( 1906 'default' => 'publish', 1907 'description' => __( 'Limit result set to posts assigned a specific status; can be comma-delimited list of status types.' ), 1908 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ), 1909 'sanitize_callback' => 'sanitize_key', 1910 'type' => 'string', 1911 'validate_callback' => array( $this, 'validate_user_can_query_private_statuses' ), 1912 ); 1913 $params['filter'] = array( 1914 'description' => __( 'Use WP Query arguments to modify the response; private query vars require appropriate authorization.' ), 1915 ); 1916 1917 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 1918 foreach ( $taxonomies as $taxonomy ) { 1919 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1920 1921 $params[ $base ] = array( 1922 'description' => sprintf( __( 'Limit result set to all items that have the specified term assigned in the %s taxonomy.' ), $base ), 1923 'type' => 'array', 1924 'sanitize_callback' => 'wp_parse_id_list', 1925 'default' => array(), 1926 ); 1927 } 1928 1929 if ( 'post' === $this->post_type ) { 1930 $params['sticky'] = array( 1931 'description' => __( 'Limit result set to items that are sticky.' ), 1932 'type' => 'boolean', 1933 'sanitize_callback' => 'rest_parse_request_arg', 1934 ); 1935 } 1936 1937 return $params; 1938 } 1939 1940 /** 1941 * Validate whether the user can query private statuses. 1942 * 1943 * @param mixed $value Post status. 1944 * @param WP_REST_Request $request Full details about the request. 1945 * @param string $parameter 1946 * @return WP_Error|boolean 1947 */ 1948 public function validate_user_can_query_private_statuses( $value, $request, $parameter ) { 1949 if ( 'publish' === $value ) { 1950 return true; 1951 } 1952 $post_type_obj = get_post_type_object( $this->post_type ); 1953 if ( current_user_can( $post_type_obj->cap->edit_posts ) ) { 1954 return true; 1955 } 1956 return new WP_Error( 'rest_forbidden_status', __( 'Status is forbidden.' ), array( 'status' => rest_authorization_required_code() ) ); 1957 } 1958 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 class WP_REST_Revisions_Controller extends WP_REST_Controller { 4 5 private $parent_post_type; 6 private $parent_controller; 7 private $parent_base; 8 9 public function __construct( $parent_post_type ) { 10 $this->parent_post_type = $parent_post_type; 11 $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); 12 $this->namespace = 'wp/v2'; 13 $this->rest_base = 'revisions'; 14 $post_type_object = get_post_type_object( $parent_post_type ); 15 $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; 16 } 17 18 /** 19 * Register routes for revisions based on post types supporting revisions 20 * 21 * @access public 22 */ 23 public function register_routes() { 24 25 register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array( 26 array( 27 'methods' => WP_REST_Server::READABLE, 28 'callback' => array( $this, 'get_items' ), 29 'permission_callback' => array( $this, 'get_items_permissions_check' ), 30 'args' => $this->get_collection_params(), 31 ), 32 'schema' => array( $this, 'get_public_item_schema' ), 33 ) ); 34 35 register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array( 36 array( 37 'methods' => WP_REST_Server::READABLE, 38 'callback' => array( $this, 'get_item' ), 39 'permission_callback' => array( $this, 'get_item_permissions_check' ), 40 'args' => array( 41 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 42 ), 43 ), 44 array( 45 'methods' => WP_REST_Server::DELETABLE, 46 'callback' => array( $this, 'delete_item' ), 47 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 48 ), 49 'schema' => array( $this, 'get_public_item_schema' ), 50 )); 51 52 } 53 54 /** 55 * Check if a given request has access to get revisions 56 * 57 * @access public 58 * 59 * @param WP_REST_Request $request Full data about the request. 60 * @return WP_Error|boolean 61 */ 62 public function get_items_permissions_check( $request ) { 63 64 $parent = $this->get_post( $request['parent'] ); 65 if ( ! $parent ) { 66 return true; 67 } 68 $parent_post_type_obj = get_post_type_object( $parent->post_type ); 69 if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) { 70 return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) ); 71 } 72 73 return true; 74 } 75 76 /** 77 * Get a collection of revisions 78 * 79 * @access public 80 * 81 * @param WP_REST_Request $request Full data about the request. 82 * @return WP_Error|WP_REST_Response 83 */ 84 public function get_items( $request ) { 85 86 $parent = $this->get_post( $request['parent'] ); 87 if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) { 88 return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent id.' ), array( 'status' => 404 ) ); 89 } 90 91 $revisions = wp_get_post_revisions( $request['parent'] ); 92 93 $response = array(); 94 foreach ( $revisions as $revision ) { 95 $data = $this->prepare_item_for_response( $revision, $request ); 96 $response[] = $this->prepare_response_for_collection( $data ); 97 } 98 return rest_ensure_response( $response ); 99 } 100 101 /** 102 * Check if a given request has access to get a specific revision 103 * 104 * @access public 105 * 106 * @param WP_REST_Request $request Full data about the request. 107 * @return WP_Error|boolean 108 */ 109 public function get_item_permissions_check( $request ) { 110 return $this->get_items_permissions_check( $request ); 111 } 112 113 /** 114 * Get one revision from the collection 115 * 116 * @access public 117 * 118 * @param WP_REST_Request $request Full data about the request. 119 * @return WP_Error|array 120 */ 121 public function get_item( $request ) { 122 123 $parent = $this->get_post( $request['parent'] ); 124 if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) { 125 return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent id.' ), array( 'status' => 404 ) ); 126 } 127 128 $revision = $this->get_post( $request['id'] ); 129 if ( ! $revision || 'revision' !== $revision->post_type ) { 130 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision id.' ), array( 'status' => 404 ) ); 131 } 132 133 $response = $this->prepare_item_for_response( $revision, $request ); 134 return rest_ensure_response( $response ); 135 } 136 137 /** 138 * Check if a given request has access to delete a revision 139 * 140 * @access public 141 * 142 * @param WP_REST_Request $request Full details about the request. 143 * @return WP_Error|boolean 144 */ 145 public function delete_item_permissions_check( $request ) { 146 147 $response = $this->get_items_permissions_check( $request ); 148 if ( ! $response || is_wp_error( $response ) ) { 149 return $response; 150 } 151 152 $post = $this->get_post( $request['id'] ); 153 if ( ! $post ) { 154 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision id.' ), array( 'status' => 404 ) ); 155 } 156 $post_type = get_post_type_object( 'revision' ); 157 return current_user_can( $post_type->cap->delete_post, $post->ID ); 158 } 159 160 /** 161 * Delete a single revision 162 * 163 * @access public 164 * 165 * @param WP_REST_Request $request Full details about the request. 166 * @return WP_Error|boolean 167 */ 168 public function delete_item( $request ) { 169 $result = wp_delete_post( $request['id'], true ); 170 171 /** 172 * Fires after a revision is deleted via the REST API. 173 * 174 * @param (mixed) $result The revision object (if it was deleted or moved to the trash successfully) 175 * or false (failure). If the revision was moved to to the trash, $result represents 176 * its new state; if it was deleted, $result represents its state before deletion. 177 * @param WP_REST_Request $request The request sent to the API. 178 */ 179 do_action( 'rest_delete_revision', $result, $request ); 180 181 if ( $result ) { 182 return true; 183 } else { 184 return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); 185 } 186 } 187 188 /** 189 * Prepare the revision for the REST response 190 * 191 * @access public 192 * 193 * @param WP_Post $post Post revision object. 194 * @param WP_REST_Request $request Request object. 195 * @return WP_REST_Response $response 196 */ 197 public function prepare_item_for_response( $post, $request ) { 198 199 $schema = $this->get_item_schema(); 200 201 $data = array(); 202 203 if ( ! empty( $schema['properties']['author'] ) ) { 204 $data['author'] = $post->post_author; 205 } 206 207 if ( ! empty( $schema['properties']['date'] ) ) { 208 $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); 209 } 210 211 if ( ! empty( $schema['properties']['date_gmt'] ) ) { 212 $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); 213 } 214 215 if ( ! empty( $schema['properties']['id'] ) ) { 216 $data['id'] = $post->ID; 217 } 218 219 if ( ! empty( $schema['properties']['modified'] ) ) { 220 $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); 221 } 222 223 if ( ! empty( $schema['properties']['modified_gmt'] ) ) { 224 $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); 225 } 226 227 if ( ! empty( $schema['properties']['parent'] ) ) { 228 $data['parent'] = (int) $post->post_parent; 229 } 230 231 if ( ! empty( $schema['properties']['slug'] ) ) { 232 $data['slug'] = $post->post_name; 233 } 234 235 if ( ! empty( $schema['properties']['guid'] ) ) { 236 $data['guid'] = array( 237 /** This filter is documented in wp-includes/post-template.php */ 238 'rendered' => apply_filters( 'get_the_guid', $post->guid ), 239 'raw' => $post->guid, 240 ); 241 } 242 243 if ( ! empty( $schema['properties']['title'] ) ) { 244 $data['title'] = array( 245 'raw' => $post->post_title, 246 'rendered' => get_the_title( $post->ID ), 247 ); 248 } 249 250 if ( ! empty( $schema['properties']['content'] ) ) { 251 252 $data['content'] = array( 253 'raw' => $post->post_content, 254 /** This filter is documented in wp-includes/post-template.php */ 255 'rendered' => apply_filters( 'the_content', $post->post_content ), 256 ); 257 } 258 259 if ( ! empty( $schema['properties']['excerpt'] ) ) { 260 $data['excerpt'] = array( 261 'raw' => $post->post_excerpt, 262 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ), 263 ); 264 } 265 266 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 267 $data = $this->add_additional_fields_to_object( $data, $request ); 268 $data = $this->filter_response_by_context( $data, $context ); 269 $response = rest_ensure_response( $data ); 270 271 if ( ! empty( $data['parent'] ) ) { 272 $response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) ); 273 } 274 275 /** 276 * Filter a revision returned from the API. 277 * 278 * Allows modification of the revision right before it is returned. 279 * 280 * @param WP_REST_Response $response The response object. 281 * @param WP_Post $post The original revision object. 282 * @param WP_REST_Request $request Request used to generate the response. 283 */ 284 return apply_filters( 'rest_prepare_revision', $response, $post, $request ); 285 } 286 287 /** 288 * Check the post_date_gmt or modified_gmt and prepare any post or 289 * modified date for single post output. 290 * 291 * @access protected 292 * 293 * @param string $date_gmt GMT publication time. 294 * @param string|null $date Optional, default is null. Local publication time. 295 * @return string|null ISO8601/RFC3339 formatted datetime. 296 */ 297 protected function prepare_date_response( $date_gmt, $date = null ) { 298 if ( '0000-00-00 00:00:00' === $date_gmt ) { 299 return null; 300 } 301 302 if ( isset( $date ) ) { 303 return mysql_to_rfc3339( $date ); 304 } 305 306 return mysql_to_rfc3339( $date_gmt ); 307 } 308 309 /** 310 * Get the revision's schema, conforming to JSON Schema 311 * 312 * @access public 313 * 314 * @return array 315 */ 316 public function get_item_schema() { 317 $schema = array( 318 '$schema' => 'http://json-schema.org/draft-04/schema#', 319 'title' => "{$this->parent_post_type}-revision", 320 'type' => 'object', 321 /* 322 * Base properties for every Revision 323 */ 324 'properties' => array( 325 'author' => array( 326 'description' => __( 'The id for the author of the object.' ), 327 'type' => 'integer', 328 'context' => array( 'view', 'edit', 'embed' ), 329 ), 330 'date' => array( 331 'description' => __( 'The date the object was published.' ), 332 'type' => 'string', 333 'format' => 'date-time', 334 'context' => array( 'view', 'edit', 'embed' ), 335 ), 336 'date_gmt' => array( 337 'description' => __( 'The date the object was published, as GMT.' ), 338 'type' => 'string', 339 'format' => 'date-time', 340 'context' => array( 'view', 'edit' ), 341 ), 342 'guid' => array( 343 'description' => __( 'GUID for the object, as it exists in the database.' ), 344 'type' => 'string', 345 'context' => array( 'view', 'edit' ), 346 ), 347 'id' => array( 348 'description' => __( 'Unique identifier for the object.' ), 349 'type' => 'integer', 350 'context' => array( 'view', 'edit', 'embed' ), 351 ), 352 'modified' => array( 353 'description' => __( 'The date the object was last modified.' ), 354 'type' => 'string', 355 'format' => 'date-time', 356 'context' => array( 'view', 'edit' ), 357 ), 358 'modified_gmt' => array( 359 'description' => __( 'The date the object was last modified, as GMT.' ), 360 'type' => 'string', 361 'format' => 'date-time', 362 'context' => array( 'view', 'edit' ), 363 ), 364 'parent' => array( 365 'description' => __( 'The id for the parent of the object.' ), 366 'type' => 'integer', 367 'context' => array( 'view', 'edit', 'embed' ), 368 ), 369 'slug' => array( 370 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), 371 'type' => 'string', 372 'context' => array( 'view', 'edit', 'embed' ), 373 ), 374 ), 375 ); 376 377 $parent_schema = $this->parent_controller->get_item_schema(); 378 379 if ( ! empty( $parent_schema['properties']['title'] ) ) { 380 $schema['properties']['title'] = $parent_schema['properties']['title']; 381 } 382 if ( ! empty( $parent_schema['properties']['content'] ) ) { 383 $schema['properties']['content'] = $parent_schema['properties']['content']; 384 } 385 if ( ! empty( $parent_schema['properties']['excerpt'] ) ) { 386 $schema['properties']['excerpt'] = $parent_schema['properties']['excerpt']; 387 } 388 if ( ! empty( $parent_schema['properties']['guid'] ) ) { 389 $schema['properties']['guid'] = $parent_schema['properties']['guid']; 390 } 391 392 return $this->add_additional_fields_schema( $schema ); 393 } 394 395 /** 396 * Get the query params for collections 397 * 398 * @access public 399 * 400 * @return array 401 */ 402 public function get_collection_params() { 403 return array( 404 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 405 ); 406 } 407 408 /** 409 * Check the post excerpt and prepare it for single post output. 410 * 411 * @access protected 412 * 413 * @param string $excerpt The post excerpt. 414 * @param WP_Post $post Post revision object. 415 * @return string|null $excerpt 416 */ 417 protected function prepare_excerpt_response( $excerpt, $post ) { 418 419 /** This filter is documented in wp-includes/post-template.php */ 420 $excerpt = apply_filters( 'the_excerpt', $excerpt, $post ); 421 422 if ( empty( $excerpt ) ) { 423 return ''; 424 } 425 426 return $excerpt; 427 } 428 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 /** 4 * Manage a WordPress site's settings. 5 */ 6 7 class WP_REST_Settings_Controller extends WP_REST_Controller { 8 9 protected $rest_base = 'settings'; 10 protected $namespace = 'wp/v2'; 11 12 /** 13 * Register the routes for the objects of the controller. 14 */ 15 public function register_routes() { 16 register_rest_route( $this->namespace, '/' . $this->rest_base, array( 17 array( 18 'methods' => WP_REST_Server::READABLE, 19 'callback' => array( $this, 'get_item' ), 20 'args' => array(), 21 'permission_callback' => array( $this, 'get_item_permissions_check' ), 22 ), 23 array( 24 'methods' => WP_REST_Server::EDITABLE, 25 'callback' => array( $this, 'update_item' ), 26 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 27 'permission_callback' => array( $this, 'get_item_permissions_check' ), 28 ), 29 'schema' => array( $this, 'get_public_item_schema' ), 30 ) ); 31 } 32 33 /** 34 * Check if a given request has access to read and manage settings. 35 * 36 * @param WP_REST_Request $request Full details about the request. 37 * @return boolean 38 */ 39 public function get_item_permissions_check( $request ) { 40 return current_user_can( 'manage_options' ); 41 } 42 43 /** 44 * Get the settings. 45 * 46 * @param WP_REST_Request $request Full details about the request. 47 * @return WP_Error|array 48 */ 49 public function get_item( $request ) { 50 $options = $this->get_registered_options(); 51 $response = array(); 52 53 foreach ( $options as $name => $args ) { 54 /** 55 * Filters the value of a setting recognized by the REST API. 56 * 57 * Allow hijacking the setting value and overriding the built-in behavior by returning a 58 * non-null value. The returned value will be presented as the setting value instead. 59 * 60 * @since 4.7.0 61 * 62 * @param mixed $result Value to use for the requested setting. Can be a scalar 63 * matching the registered schema for the setting, or null to 64 * follow the default `get_option` behavior. 65 * @param string $name Setting name (as shown in REST API responses). 66 * @param array $args Arguments passed to `register_setting()` for this setting. 67 */ 68 $response[ $name ] = apply_filters( 'rest_pre_get_setting', null, $name, $args ); 69 70 if ( is_null( $response[ $name ] ) ) { 71 // Default to a null value as "null" in the response means "not set". 72 $response[ $name ] = get_option( $args['option_name'], $args['schema']['default'] ); 73 } 74 75 // Because get_option() is lossy, we have to 76 // cast values to the type they are registered with. 77 $response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] ); 78 } 79 80 return $response; 81 } 82 83 /** 84 * Prepare a value for output based off a schema array. 85 * 86 * @param mixed $value 87 * @param array $schema 88 * @return mixed 89 */ 90 protected function prepare_value( $value, $schema ) { 91 switch ( $schema['type'] ) { 92 case 'string': 93 return (string) $value; 94 case 'number': 95 return (float) $value; 96 case 'boolean': 97 return (bool) $value; 98 default: 99 return null; 100 } 101 } 102 103 /** 104 * Update settings for the settings object. 105 * 106 * @param WP_REST_Request $request Full detail about the request. 107 * @return WP_Error|array 108 */ 109 public function update_item( $request ) { 110 $options = $this->get_registered_options(); 111 $params = $request->get_params(); 112 113 foreach ( $options as $name => $args ) { 114 if ( ! array_key_exists( $name, $params ) ) { 115 continue; 116 } 117 118 /** 119 * Filters whether to preempt a setting value update. 120 * 121 * Allow hijacking the setting update logic and overriding the built-in behavior by 122 * returning true. 123 * 124 * @since 4.7.0 125 * 126 * @param boolean $result Whether to override the default behavior for updating the 127 * value of a setting. 128 * @param string $name Setting name (as shown in REST API responses). 129 * @param mixed $value Updated setting value. 130 * @param array $args Arguments passed to `register_setting()` for this setting. 131 */ 132 $updated = apply_filters( 'rest_pre_update_setting', false, $name, $request[ $name ], $args ); 133 if ( $updated ) { 134 continue; 135 } 136 137 // A null value means reset the option, which is essentially deleting it 138 // from the database and then relying on the default value. 139 if ( is_null( $request[ $name ] ) ) { 140 delete_option( $args['option_name'] ); 141 } else { 142 update_option( $args['option_name'], $request[ $name ] ); 143 } 144 } 145 146 return $this->get_item( $request ); 147 } 148 149 /** 150 * Get all the registered options for the Settings API 151 * 152 * @return array 153 */ 154 protected function get_registered_options() { 155 $rest_options = array(); 156 157 foreach ( get_registered_settings() as $name => $args ) { 158 if ( empty( $args['show_in_rest'] ) ) { 159 continue; 160 } 161 162 $rest_args = array(); 163 if ( is_array( $args['show_in_rest'] ) ) { 164 $rest_args = $args['show_in_rest']; 165 } 166 167 $defaults = array( 168 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name, 169 'schema' => array(), 170 ); 171 $rest_args = array_merge( $defaults, $rest_args ); 172 173 $default_schema = array( 174 'type' => empty( $args['type'] ) ? null : $args['type'], 175 'description' => empty( $args['description'] ) ? '' : $args['description'], 176 'default' => isset( $args['default'] ) ? $args['default'] : null, 177 ); 178 179 $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); 180 $rest_args['option_name'] = $name; 181 182 // Skip over settings that don't have a defined type in the schema. 183 if ( empty( $rest_args['schema']['type'] ) ) { 184 continue; 185 } 186 187 // Whitelist the supported types for settings, as we don't want invalid types 188 // to be updated with arbitrary values that we can't do decent sanitizing for. 189 if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'string', 'boolean' ), true ) ) { 190 continue; 191 } 192 193 $rest_options[ $rest_args['name'] ] = $rest_args; 194 } 195 196 return $rest_options; 197 } 198 199 /** 200 * Get the site setting schema, conforming to JSON Schema. 201 * 202 * @return array 203 */ 204 public function get_item_schema() { 205 $options = $this->get_registered_options(); 206 207 $schema = array( 208 '$schema' => 'http://json-schema.org/draft-04/schema#', 209 'title' => 'settings', 210 'type' => 'object', 211 'properties' => array(), 212 ); 213 214 foreach ( $options as $option_name => $option ) { 215 $schema['properties'][ $option_name ] = $option['schema']; 216 } 217 218 return $this->add_additional_fields_schema( $schema ); 219 } 220 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php
1 <?php 2 3 class WP_REST_Taxonomies_Controller extends WP_REST_Controller { 4 5 public function __construct() { 6 $this->namespace = 'wp/v2'; 7 $this->rest_base = 'taxonomies'; 8 } 9 10 /** 11 * Register the routes for the objects of the controller. 12 */ 13 public function register_routes() { 14 15 register_rest_route( $this->namespace, '/' . $this->rest_base, array( 16 array( 17 'methods' => WP_REST_Server::READABLE, 18 'callback' => array( $this, 'get_items' ), 19 'permission_callback' => array( $this, 'get_items_permissions_check' ), 20 'args' => $this->get_collection_params(), 21 ), 22 'schema' => array( $this, 'get_public_item_schema' ), 23 ) ); 24 25 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<taxonomy>[\w-]+)', array( 26 array( 27 'methods' => WP_REST_Server::READABLE, 28 'callback' => array( $this, 'get_item' ), 29 'permission_callback' => array( $this, 'get_item_permissions_check' ), 30 'args' => array( 31 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 32 ), 33 ), 34 'schema' => array( $this, 'get_public_item_schema' ), 35 ) ); 36 } 37 38 /** 39 * Check whether a given request has permission to read taxonomies. 40 * 41 * @param WP_REST_Request $request Full details about the request. 42 * @return WP_Error|boolean 43 */ 44 public function get_items_permissions_check( $request ) { 45 if ( 'edit' === $request['context'] ) { 46 if ( ! empty( $request['type'] ) ) { 47 $taxonomies = get_object_taxonomies( $request['type'], 'objects' ); 48 } else { 49 $taxonomies = get_taxonomies( '', 'objects' ); 50 } 51 foreach ( $taxonomies as $taxonomy ) { 52 if ( ! empty( $taxonomy->show_in_rest ) && current_user_can( $taxonomy->cap->manage_terms ) ) { 53 return true; 54 } 55 } 56 return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 57 } 58 return true; 59 } 60 61 /** 62 * Get all public taxonomies 63 * 64 * @param WP_REST_Request $request 65 * @return array 66 */ 67 public function get_items( $request ) { 68 69 // Retrieve the list of registered collection query parameters. 70 $registered = $this->get_collection_params(); 71 72 if ( isset( $registered['type'] ) && ! empty( $request['type'] ) ) { 73 $taxonomies = get_object_taxonomies( $request['type'], 'objects' ); 74 } else { 75 $taxonomies = get_taxonomies( '', 'objects' ); 76 } 77 $data = array(); 78 foreach ( $taxonomies as $tax_type => $value ) { 79 if ( empty( $value->show_in_rest ) || ( 'edit' === $request['context'] && ! current_user_can( $value->cap->manage_terms ) ) ) { 80 continue; 81 } 82 $tax = $this->prepare_item_for_response( $value, $request ); 83 $tax = $this->prepare_response_for_collection( $tax ); 84 $data[ $tax_type ] = $tax; 85 } 86 87 if ( empty( $data ) ) { 88 // Response should still be returned as a JSON object when it is empty. 89 $data = (object) $data; 90 } 91 92 return rest_ensure_response( $data ); 93 } 94 95 /** 96 * Check if a given request has access a taxonomy 97 * 98 * @param WP_REST_Request $request Full details about the request. 99 * @return WP_Error|boolean 100 */ 101 public function get_item_permissions_check( $request ) { 102 103 $tax_obj = get_taxonomy( $request['taxonomy'] ); 104 105 if ( $tax_obj ) { 106 if ( empty( $tax_obj->show_in_rest ) ) { 107 return false; 108 } 109 if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->manage_terms ) ) { 110 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this resource.' ), array( 'status' => rest_authorization_required_code() ) ); 111 } 112 } 113 114 return true; 115 } 116 117 /** 118 * Get a specific taxonomy 119 * 120 * @param WP_REST_Request $request 121 * @return array|WP_Error 122 */ 123 public function get_item( $request ) { 124 $tax_obj = get_taxonomy( $request['taxonomy'] ); 125 if ( empty( $tax_obj ) ) { 126 return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) ); 127 } 128 $data = $this->prepare_item_for_response( $tax_obj, $request ); 129 return rest_ensure_response( $data ); 130 } 131 132 /** 133 * Prepare a taxonomy object for serialization 134 * 135 * @param stdClass $taxonomy Taxonomy data 136 * @param WP_REST_Request $request 137 * @return WP_REST_Response $response 138 */ 139 public function prepare_item_for_response( $taxonomy, $request ) { 140 141 $data = array( 142 'name' => $taxonomy->label, 143 'slug' => $taxonomy->name, 144 'capabilities' => $taxonomy->cap, 145 'description' => $taxonomy->description, 146 'labels' => $taxonomy->labels, 147 'types' => $taxonomy->object_type, 148 'show_cloud' => $taxonomy->show_tagcloud, 149 'hierarchical' => $taxonomy->hierarchical, 150 ); 151 152 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 153 $data = $this->add_additional_fields_to_object( $data, $request ); 154 $data = $this->filter_response_by_context( $data, $context ); 155 156 // Wrap the data in a response object. 157 $response = rest_ensure_response( $data ); 158 159 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 160 $response->add_links( array( 161 'collection' => array( 162 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 163 ), 164 'https://api.w.org/items' => array( 165 'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ), 166 ), 167 ) ); 168 169 /** 170 * Filter a taxonomy returned from the API. 171 * 172 * Allows modification of the taxonomy data right before it is returned. 173 * 174 * @param WP_REST_Response $response The response object. 175 * @param object $item The original taxonomy object. 176 * @param WP_REST_Request $request Request used to generate the response. 177 */ 178 return apply_filters( 'rest_prepare_taxonomy', $response, $taxonomy, $request ); 179 } 180 181 /** 182 * Get the taxonomy's schema, conforming to JSON Schema 183 * 184 * @return array 185 */ 186 public function get_item_schema() { 187 $schema = array( 188 '$schema' => 'http://json-schema.org/draft-04/schema#', 189 'title' => 'taxonomy', 190 'type' => 'object', 191 'properties' => array( 192 'capabilities' => array( 193 'description' => __( 'All capabilities used by the resource.' ), 194 'type' => 'array', 195 'context' => array( 'edit' ), 196 'readonly' => true, 197 ), 198 'description' => array( 199 'description' => __( 'A human-readable description of the resource.' ), 200 'type' => 'string', 201 'context' => array( 'view', 'edit' ), 202 'readonly' => true, 203 ), 204 'hierarchical' => array( 205 'description' => __( 'Whether or not the resource should have children.' ), 206 'type' => 'boolean', 207 'context' => array( 'view', 'edit' ), 208 'readonly' => true, 209 ), 210 'labels' => array( 211 'description' => __( 'Human-readable labels for the resource for various contexts.' ), 212 'type' => 'object', 213 'context' => array( 'edit' ), 214 'readonly' => true, 215 ), 216 'name' => array( 217 'description' => __( 'The title for the resource.' ), 218 'type' => 'string', 219 'context' => array( 'view', 'edit', 'embed' ), 220 'readonly' => true, 221 ), 222 'slug' => array( 223 'description' => __( 'An alphanumeric identifier for the resource.' ), 224 'type' => 'string', 225 'context' => array( 'view', 'edit', 'embed' ), 226 'readonly' => true, 227 ), 228 'show_cloud' => array( 229 'description' => __( 'Whether or not the term cloud should be displayed.' ), 230 'type' => 'boolean', 231 'context' => array( 'edit' ), 232 'readonly' => true, 233 ), 234 'types' => array( 235 'description' => __( 'Types associated with resource.' ), 236 'type' => 'array', 237 'context' => array( 'view', 'edit' ), 238 'readonly' => true, 239 ), 240 ), 241 ); 242 return $this->add_additional_fields_schema( $schema ); 243 } 244 245 /** 246 * Get the query params for collections 247 * 248 * @return array 249 */ 250 public function get_collection_params() { 251 $new_params = array(); 252 $new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); 253 $new_params['type'] = array( 254 'description' => __( 'Limit results to resources associated with a specific post type.' ), 255 'type' => 'string', 256 'validate_callback' => 'rest_validate_request_arg', 257 ); 258 return $new_params; 259 } 260 261 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 /** 4 * Access terms associated with a taxonomy. 5 */ 6 class WP_REST_Terms_Controller extends WP_REST_Controller { 7 8 /** 9 * Taxonomy key. 10 * 11 * @access protected 12 * @var string 13 */ 14 protected $taxonomy; 15 16 /** 17 * Instance of a term meta fields object. 18 * 19 * @access protected 20 * @var WP_REST_Term_Meta_Fields 21 */ 22 protected $meta; 23 24 /** 25 * Column to have the terms be sorted by. 26 * 27 * @access protected 28 * @var string 29 */ 30 protected $sort_column; 31 32 /** 33 * Number of terms that were found. 34 * 35 * @access protected 36 * @var int 37 */ 38 protected $total_terms; 39 40 /** 41 * Constructor. 42 * 43 * @param string $taxonomy Taxonomy key. 44 */ 45 public function __construct( $taxonomy ) { 46 $this->taxonomy = $taxonomy; 47 $this->namespace = 'wp/v2'; 48 $tax_obj = get_taxonomy( $taxonomy ); 49 $this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name; 50 51 $this->meta = new WP_REST_Term_Meta_Fields( $taxonomy ); 52 } 53 54 /** 55 * Registers the routes for the objects of the controller. 56 */ 57 public function register_routes() { 58 59 register_rest_route( $this->namespace, '/' . $this->rest_base, array( 60 array( 61 'methods' => WP_REST_Server::READABLE, 62 'callback' => array( $this, 'get_items' ), 63 'permission_callback' => array( $this, 'get_items_permissions_check' ), 64 'args' => $this->get_collection_params(), 65 ), 66 array( 67 'methods' => WP_REST_Server::CREATABLE, 68 'callback' => array( $this, 'create_item' ), 69 'permission_callback' => array( $this, 'create_item_permissions_check' ), 70 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 71 ), 72 'schema' => array( $this, 'get_public_item_schema' ), 73 ) ); 74 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 75 array( 76 'methods' => WP_REST_Server::READABLE, 77 'callback' => array( $this, 'get_item' ), 78 'permission_callback' => array( $this, 'get_item_permissions_check' ), 79 'args' => array( 80 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 81 ), 82 ), 83 array( 84 'methods' => WP_REST_Server::EDITABLE, 85 'callback' => array( $this, 'update_item' ), 86 'permission_callback' => array( $this, 'update_item_permissions_check' ), 87 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 88 ), 89 array( 90 'methods' => WP_REST_Server::DELETABLE, 91 'callback' => array( $this, 'delete_item' ), 92 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 93 'args' => array( 94 'force' => array( 95 'default' => false, 96 'description' => __( 'Required to be true, as resource does not support trashing.' ), 97 ), 98 ), 99 ), 100 'schema' => array( $this, 'get_public_item_schema' ), 101 ) ); 102 } 103 104 /** 105 * Checks if a request has access to read terms in the specified taxonomy. 106 * 107 * @param WP_REST_Request $request Full details about the request. 108 * @return WP_Error|boolean 109 */ 110 public function get_items_permissions_check( $request ) { 111 $tax_obj = get_taxonomy( $this->taxonomy ); 112 if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { 113 return false; 114 } 115 if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) { 116 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 117 } 118 return true; 119 } 120 121 /** 122 * Gets terms associated with a taxonomy. 123 * 124 * @param WP_REST_Request $request Full details about the request. 125 * @return WP_REST_Response|WP_Error 126 */ 127 public function get_items( $request ) { 128 129 // Retrieve the list of registered collection query parameters. 130 $registered = $this->get_collection_params(); 131 132 // This array defines mappings between public API query parameters whose 133 // values are accepted as-passed, and their internal WP_Query parameter 134 // name equivalents (some are the same). Only values which are also 135 // present in $registered will be set. 136 $parameter_mappings = array( 137 'exclude' => 'exclude', 138 'include' => 'include', 139 'order' => 'order', 140 'orderby' => 'orderby', 141 'post' => 'post', 142 'hide_empty' => 'hide_empty', 143 'per_page' => 'number', 144 'search' => 'search', 145 'slug' => 'slug', 146 ); 147 148 $prepared_args = array(); 149 150 // For each known parameter which is both registered and present in the request, 151 // set the parameter's value on the query $prepared_args. 152 foreach ( $parameter_mappings as $api_param => $wp_param ) { 153 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { 154 $prepared_args[ $wp_param ] = $request[ $api_param ]; 155 } 156 } 157 158 if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) { 159 $prepared_args['offset'] = $request['offset']; 160 } else { 161 $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; 162 } 163 164 $taxonomy_obj = get_taxonomy( $this->taxonomy ); 165 166 if ( $taxonomy_obj->hierarchical && isset( $registered['parent'], $request['parent'] ) ) { 167 if ( 0 === $request['parent'] ) { 168 // Only query top-level terms. 169 $prepared_args['parent'] = 0; 170 } else { 171 if ( $request['parent'] ) { 172 $prepared_args['parent'] = $request['parent']; 173 } 174 } 175 } 176 177 /** 178 * Filters the query arguments before passing them to get_terms(). 179 * 180 * Enables adding extra arguments or setting defaults for a terms 181 * collection request. 182 * 183 * @see https://developer.wordpress.org/reference/functions/get_terms/ 184 * 185 * @param array $prepared_args Array of arguments to be 186 * passed to get_terms(). 187 * @param WP_REST_Request $request The current request. 188 */ 189 $prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request ); 190 191 if ( ! empty( $prepared_args['post'] ) ) { 192 $query_result = $this->get_terms_for_post( $prepared_args ); 193 $total_terms = $this->total_terms; 194 } else { 195 $query_result = get_terms( $this->taxonomy, $prepared_args ); 196 197 $count_args = $prepared_args; 198 unset( $count_args['number'], $count_args['offset'] ); 199 $total_terms = wp_count_terms( $this->taxonomy, $count_args ); 200 201 // wp_count_terms can return a falsy value when the term has no children 202 if ( ! $total_terms ) { 203 $total_terms = 0; 204 } 205 } 206 $response = array(); 207 foreach ( $query_result as $term ) { 208 $data = $this->prepare_item_for_response( $term, $request ); 209 $response[] = $this->prepare_response_for_collection( $data ); 210 } 211 212 $response = rest_ensure_response( $response ); 213 214 // Store pagination values for headers. 215 $per_page = (int) $prepared_args['number']; 216 $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); 217 218 $response->header( 'X-WP-Total', (int) $total_terms ); 219 $max_pages = ceil( $total_terms / $per_page ); 220 $response->header( 'X-WP-TotalPages', (int) $max_pages ); 221 222 $base = add_query_arg( $request->get_query_params(), rest_url( $this->namespace . '/' . $this->rest_base ) ); 223 if ( $page > 1 ) { 224 $prev_page = $page - 1; 225 if ( $prev_page > $max_pages ) { 226 $prev_page = $max_pages; 227 } 228 $prev_link = add_query_arg( 'page', $prev_page, $base ); 229 $response->link_header( 'prev', $prev_link ); 230 } 231 if ( $max_pages > $page ) { 232 $next_page = $page + 1; 233 $next_link = add_query_arg( 'page', $next_page, $base ); 234 $response->link_header( 'next', $next_link ); 235 } 236 237 return $response; 238 } 239 240 /** 241 * Gets the terms attached to a post. 242 * 243 * This is an alternative to get_terms() that uses get_the_terms() 244 * instead, which hits the object cache. There are a few things not 245 * supported, notably `include`, `exclude`. In `self::get_items()` these 246 * are instead treated as a full query. 247 * 248 * @param array $prepared_args Arguments for get_terms(). 249 * @return array List of term objects. (Total count in `$this->total_terms`) 250 */ 251 protected function get_terms_for_post( $prepared_args ) { 252 $query_result = get_the_terms( $prepared_args['post'], $this->taxonomy ); 253 if ( empty( $query_result ) ) { 254 $this->total_terms = 0; 255 return array(); 256 } 257 258 /* 259 * get_items() verifies that we don't have `include` set, and default 260 * ordering is by `name`. 261 */ 262 if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ), true ) ) { 263 switch ( $prepared_args['orderby'] ) { 264 case 'id': 265 $this->sort_column = 'term_id'; 266 break; 267 268 case 'slug': 269 case 'term_group': 270 case 'description': 271 case 'count': 272 $this->sort_column = $prepared_args['orderby']; 273 break; 274 } 275 usort( $query_result, array( $this, 'compare_terms' ) ); 276 } 277 if ( strtolower( $prepared_args['order'] ) !== 'asc' ) { 278 $query_result = array_reverse( $query_result ); 279 } 280 281 // Pagination. 282 $this->total_terms = count( $query_result ); 283 $query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] ); 284 285 return $query_result; 286 } 287 288 /** 289 * Comparison function for sorting terms by a column. 290 * 291 * Uses `$this->sort_column` to determine field to sort by. 292 * 293 * @access protected 294 * 295 * @param stdClass $left Term object. 296 * @param stdClass $right Term object. 297 * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left. 298 */ 299 protected function compare_terms( $left, $right ) { 300 $col = $this->sort_column; 301 $left_val = $left->$col; 302 $right_val = $right->$col; 303 304 if ( is_int( $left_val ) && is_int( $right_val ) ) { 305 return $left_val - $right_val; 306 } 307 308 return strcmp( $left_val, $right_val ); 309 } 310 311 /** 312 * Checks if a request has access to read the specified term. 313 * 314 * @param WP_REST_Request $request Full details about the request. 315 * @return WP_Error|boolean 316 */ 317 public function get_item_permissions_check( $request ) { 318 $tax_obj = get_taxonomy( $this->taxonomy ); 319 if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { 320 return false; 321 } 322 if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) { 323 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 324 } 325 return true; 326 } 327 328 /** 329 * Gets a single term from a taxonomy. 330 * 331 * @param WP_REST_Request $request Full details about the request 332 * @return WP_REST_Request|WP_Error 333 */ 334 public function get_item( $request ) { 335 336 $term = get_term( (int) $request['id'], $this->taxonomy ); 337 if ( ! $term || $term->taxonomy !== $this->taxonomy ) { 338 return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) ); 339 } 340 if ( is_wp_error( $term ) ) { 341 return $term; 342 } 343 344 $response = $this->prepare_item_for_response( $term, $request ); 345 346 return rest_ensure_response( $response ); 347 } 348 349 /** 350 * Checks if a request has access to create a term. 351 * 352 * @param WP_REST_Request $request Full details about the request. 353 * @return WP_Error|boolean 354 */ 355 public function create_item_permissions_check( $request ) { 356 357 if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { 358 return false; 359 } 360 361 $taxonomy_obj = get_taxonomy( $this->taxonomy ); 362 if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) { 363 return new WP_Error( 'rest_cannot_create', __( 'Sorry, you cannot create new resource.' ), array( 'status' => rest_authorization_required_code() ) ); 364 } 365 366 return true; 367 } 368 369 /** 370 * Creates a single term in a taxonomy. 371 * 372 * @param WP_REST_Request $request Full details about the request 373 * @return WP_REST_Request|WP_Error 374 */ 375 public function create_item( $request ) { 376 if ( isset( $request['parent'] ) ) { 377 if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { 378 return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); 379 } 380 381 $parent = get_term( (int) $request['parent'], $this->taxonomy ); 382 383 if ( ! $parent ) { 384 return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) ); 385 } 386 } 387 388 $prepared_term = $this->prepare_item_for_database( $request ); 389 390 $term = wp_insert_term( $prepared_term->name, $this->taxonomy, $prepared_term ); 391 if ( is_wp_error( $term ) ) { 392 393 /* 394 * If we're going to inform the client that the term already exists, 395 * give them the identifier for future use. 396 */ 397 if ( $term_id = $term->get_error_data( 'term_exists' ) ) { 398 $existing_term = get_term( $term_id, $this->taxonomy ); 399 $term->add_data( $existing_term->term_id, 'term_exists' ); 400 } 401 402 return $term; 403 } 404 405 $term = get_term( $term['term_id'], $this->taxonomy ); 406 407 /** 408 * Fires after a single term is created or updated via the REST API. 409 * 410 * @param WP_Term $term Inserted Term object. 411 * @param WP_REST_Request $request Request object. 412 * @param boolean $creating True when creating term, false when updating. 413 */ 414 do_action( "rest_insert_{$this->taxonomy}", $term, $request, true ); 415 416 $schema = $this->get_item_schema(); 417 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 418 $meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] ); 419 if ( is_wp_error( $meta_update ) ) { 420 return $meta_update; 421 } 422 } 423 424 $fields_update = $this->update_additional_fields_for_object( $term, $request ); 425 if ( is_wp_error( $fields_update ) ) { 426 return $fields_update; 427 } 428 429 $request->set_param( 'context', 'view' ); 430 $response = $this->prepare_item_for_response( $term, $request ); 431 $response = rest_ensure_response( $response ); 432 $response->set_status( 201 ); 433 $response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) ); 434 return $response; 435 } 436 437 /** 438 * Checks if a request has access to update the specified term. 439 * 440 * @param WP_REST_Request $request Full details about the request. 441 * @return WP_Error|boolean 442 */ 443 public function update_item_permissions_check( $request ) { 444 445 if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { 446 return false; 447 } 448 449 $term = get_term( (int) $request['id'], $this->taxonomy ); 450 if ( ! $term ) { 451 return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) ); 452 } 453 454 $taxonomy_obj = get_taxonomy( $this->taxonomy ); 455 if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) { 456 return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update resource.' ), array( 'status' => rest_authorization_required_code() ) ); 457 } 458 459 return true; 460 } 461 462 /** 463 * Updates a single term from a taxonomy. 464 * 465 * @param WP_REST_Request $request Full details about the request 466 * @return WP_REST_Request|WP_Error 467 */ 468 public function update_item( $request ) { 469 if ( isset( $request['parent'] ) ) { 470 if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { 471 return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); 472 } 473 474 $parent = get_term( (int) $request['parent'], $this->taxonomy ); 475 476 if ( ! $parent ) { 477 return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) ); 478 } 479 } 480 481 $prepared_term = $this->prepare_item_for_database( $request ); 482 483 $term = get_term( (int) $request['id'], $this->taxonomy ); 484 485 // Only update the term if we haz something to update. 486 if ( ! empty( $prepared_term ) ) { 487 $update = wp_update_term( $term->term_id, $term->taxonomy, (array) $prepared_term ); 488 if ( is_wp_error( $update ) ) { 489 return $update; 490 } 491 } 492 493 $term = get_term( (int) $request['id'], $this->taxonomy ); 494 495 /* This action is documented in lib/endpoints/class-wp-rest-terms-controller.php */ 496 do_action( "rest_insert_{$this->taxonomy}", $term, $request, false ); 497 498 $schema = $this->get_item_schema(); 499 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 500 $meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] ); 501 if ( is_wp_error( $meta_update ) ) { 502 return $meta_update; 503 } 504 } 505 506 $fields_update = $this->update_additional_fields_for_object( $term, $request ); 507 if ( is_wp_error( $fields_update ) ) { 508 return $fields_update; 509 } 510 511 $request->set_param( 'context', 'view' ); 512 $response = $this->prepare_item_for_response( $term, $request ); 513 return rest_ensure_response( $response ); 514 } 515 516 /** 517 * Checks if a request has access to delete the specified term. 518 * 519 * @param WP_REST_Request $request Full details about the request. 520 * @return WP_Error|boolean 521 */ 522 public function delete_item_permissions_check( $request ) { 523 if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { 524 return false; 525 } 526 $term = get_term( (int) $request['id'], $this->taxonomy ); 527 if ( ! $term ) { 528 return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) ); 529 } 530 $taxonomy_obj = get_taxonomy( $this->taxonomy ); 531 if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) { 532 return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete resource.' ), array( 'status' => rest_authorization_required_code() ) ); 533 } 534 return true; 535 } 536 537 /** 538 * Deletes a single term from a taxonomy. 539 * 540 * @param WP_REST_Request $request Full details about the request 541 * @return WP_REST_Response|WP_Error 542 */ 543 public function delete_item( $request ) { 544 545 $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 546 547 // We don't support trashing for this resource type. 548 if ( ! $force ) { 549 return new WP_Error( 'rest_trash_not_supported', __( 'Resource does not support trashing.' ), array( 'status' => 501 ) ); 550 } 551 552 $term = get_term( (int) $request['id'], $this->taxonomy ); 553 $request->set_param( 'context', 'view' ); 554 $response = $this->prepare_item_for_response( $term, $request ); 555 556 $retval = wp_delete_term( $term->term_id, $term->taxonomy ); 557 if ( ! $retval ) { 558 return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) ); 559 } 560 561 /** 562 * Fires after a single term is deleted via the REST API. 563 * 564 * @param WP_Term $term The deleted term. 565 * @param WP_REST_Response $response The response data. 566 * @param WP_REST_Request $request The request sent to the API. 567 */ 568 do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request ); 569 570 return $response; 571 } 572 573 /** 574 * Prepares a single term for create or update. 575 * 576 * @param WP_REST_Request $request Request object. 577 * @return object $prepared_term Term object. 578 */ 579 public function prepare_item_for_database( $request ) { 580 $prepared_term = new stdClass; 581 582 $schema = $this->get_item_schema(); 583 if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) { 584 $prepared_term->name = $request['name']; 585 } 586 587 if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) { 588 $prepared_term->slug = $request['slug']; 589 } 590 591 if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) { 592 $prepared_term->taxonomy = $request['taxonomy']; 593 } 594 595 if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) { 596 $prepared_term->description = $request['description']; 597 } 598 599 if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) { 600 $parent_term_id = 0; 601 $parent_term = get_term( (int) $request['parent'], $this->taxonomy ); 602 603 if ( $parent_term ) { 604 $parent_term_id = $parent_term->term_id; 605 } 606 607 $prepared_term->parent = $parent_term_id; 608 } 609 610 /** 611 * Filters term data before inserting term via the REST API. 612 * 613 * @param object $prepared_term Term object. 614 * @param WP_REST_Request $request Request object. 615 */ 616 return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request ); 617 } 618 619 /** 620 * Prepares a single term output for response. 621 * 622 * @param obj $item Term object. 623 * @param WP_REST_Request $request Request object. 624 * @return WP_REST_Response $response 625 */ 626 public function prepare_item_for_response( $item, $request ) { 627 628 $schema = $this->get_item_schema(); 629 $data = array(); 630 if ( ! empty( $schema['properties']['id'] ) ) { 631 $data['id'] = (int) $item->term_id; 632 } 633 if ( ! empty( $schema['properties']['count'] ) ) { 634 $data['count'] = (int) $item->count; 635 } 636 if ( ! empty( $schema['properties']['description'] ) ) { 637 $data['description'] = $item->description; 638 } 639 if ( ! empty( $schema['properties']['link'] ) ) { 640 $data['link'] = get_term_link( $item ); 641 } 642 if ( ! empty( $schema['properties']['name'] ) ) { 643 $data['name'] = $item->name; 644 } 645 if ( ! empty( $schema['properties']['slug'] ) ) { 646 $data['slug'] = $item->slug; 647 } 648 if ( ! empty( $schema['properties']['taxonomy'] ) ) { 649 $data['taxonomy'] = $item->taxonomy; 650 } 651 if ( ! empty( $schema['properties']['parent'] ) ) { 652 $data['parent'] = (int) $item->parent; 653 } 654 if ( ! empty( $schema['properties']['meta'] ) ) { 655 $data['meta'] = $this->meta->get_value( $item->term_id, $request ); 656 } 657 658 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 659 $data = $this->add_additional_fields_to_object( $data, $request ); 660 $data = $this->filter_response_by_context( $data, $context ); 661 662 $response = rest_ensure_response( $data ); 663 664 $response->add_links( $this->prepare_links( $item ) ); 665 666 /** 667 * Filters a term item returned from the API. 668 * 669 * Allows modification of the term data right before it is returned. 670 * 671 * @param WP_REST_Response $response The response object. 672 * @param object $item The original term object. 673 * @param WP_REST_Request $request Request used to generate the response. 674 */ 675 return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request ); 676 } 677 678 /** 679 * Prepares links for the request. 680 * 681 * @param object $term Term object. 682 * @return array Links for the given term. 683 */ 684 protected function prepare_links( $term ) { 685 $base = $this->namespace . '/' . $this->rest_base; 686 $links = array( 687 'self' => array( 688 'href' => rest_url( trailingslashit( $base ) . $term->term_id ), 689 ), 690 'collection' => array( 691 'href' => rest_url( $base ), 692 ), 693 'about' => array( 694 'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ), 695 ), 696 ); 697 698 if ( $term->parent ) { 699 $parent_term = get_term( (int) $term->parent, $term->taxonomy ); 700 if ( $parent_term ) { 701 $links['up'] = array( 702 'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ), 703 'embeddable' => true, 704 ); 705 } 706 } 707 708 $taxonomy_obj = get_taxonomy( $term->taxonomy ); 709 if ( empty( $taxonomy_obj->object_type ) ) { 710 return $links; 711 } 712 713 $post_type_links = array(); 714 foreach ( $taxonomy_obj->object_type as $type ) { 715 $post_type_object = get_post_type_object( $type ); 716 if ( empty( $post_type_object->show_in_rest ) ) { 717 continue; 718 } 719 $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; 720 $post_type_links[] = array( 721 'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( sprintf( 'wp/v2/%s', $rest_base ) ) ), 722 ); 723 } 724 if ( ! empty( $post_type_links ) ) { 725 $links['https://api.w.org/post_type'] = $post_type_links; 726 } 727 728 return $links; 729 } 730 731 /** 732 * Gets the term's schema, conforming to JSON Schema. 733 * 734 * @return array 735 */ 736 public function get_item_schema() { 737 $schema = array( 738 '$schema' => 'http://json-schema.org/draft-04/schema#', 739 'title' => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy, 740 'type' => 'object', 741 'properties' => array( 742 'id' => array( 743 'description' => __( 'Unique identifier for the resource.' ), 744 'type' => 'integer', 745 'context' => array( 'view', 'embed', 'edit' ), 746 'readonly' => true, 747 ), 748 'count' => array( 749 'description' => __( 'Number of published posts for the resource.' ), 750 'type' => 'integer', 751 'context' => array( 'view', 'edit' ), 752 'readonly' => true, 753 ), 754 'description' => array( 755 'description' => __( 'HTML description of the resource.' ), 756 'type' => 'string', 757 'context' => array( 'view', 'edit' ), 758 'arg_options' => array( 759 'sanitize_callback' => 'wp_filter_post_kses', 760 ), 761 ), 762 'link' => array( 763 'description' => __( 'URL to the resource.' ), 764 'type' => 'string', 765 'format' => 'uri', 766 'context' => array( 'view', 'embed', 'edit' ), 767 'readonly' => true, 768 ), 769 'name' => array( 770 'description' => __( 'HTML title for the resource.' ), 771 'type' => 'string', 772 'context' => array( 'view', 'embed', 'edit' ), 773 'arg_options' => array( 774 'sanitize_callback' => 'sanitize_text_field', 775 ), 776 'required' => true, 777 ), 778 'slug' => array( 779 'description' => __( 'An alphanumeric identifier for the resource unique to its type.' ), 780 'type' => 'string', 781 'context' => array( 'view', 'embed', 'edit' ), 782 'arg_options' => array( 783 'sanitize_callback' => array( $this, 'sanitize_slug' ), 784 ), 785 ), 786 'taxonomy' => array( 787 'description' => __( 'Type attribution for the resource.' ), 788 'type' => 'string', 789 'enum' => array_keys( get_taxonomies() ), 790 'context' => array( 'view', 'embed', 'edit' ), 791 'readonly' => true, 792 ), 793 ), 794 ); 795 $taxonomy = get_taxonomy( $this->taxonomy ); 796 if ( $taxonomy->hierarchical ) { 797 $schema['properties']['parent'] = array( 798 'description' => __( 'The id for the parent of the resource.' ), 799 'type' => 'integer', 800 'context' => array( 'view', 'edit' ), 801 ); 802 } 803 804 $schema['properties']['meta'] = $this->meta->get_field_schema(); 805 return $this->add_additional_fields_schema( $schema ); 806 } 807 808 /** 809 * Gets the query params for collections. 810 * 811 * @return array 812 */ 813 public function get_collection_params() { 814 $query_params = parent::get_collection_params(); 815 $taxonomy = get_taxonomy( $this->taxonomy ); 816 817 $query_params['context']['default'] = 'view'; 818 819 $query_params['exclude'] = array( 820 'description' => __( 'Ensure result set excludes specific ids.' ), 821 'type' => 'array', 822 'default' => array(), 823 'sanitize_callback' => 'wp_parse_id_list', 824 ); 825 $query_params['include'] = array( 826 'description' => __( 'Limit result set to specific ids.' ), 827 'type' => 'array', 828 'default' => array(), 829 'sanitize_callback' => 'wp_parse_id_list', 830 ); 831 if ( ! $taxonomy->hierarchical ) { 832 $query_params['offset'] = array( 833 'description' => __( 'Offset the result set by a specific number of items.' ), 834 'type' => 'integer', 835 'sanitize_callback' => 'absint', 836 'validate_callback' => 'rest_validate_request_arg', 837 ); 838 } 839 $query_params['order'] = array( 840 'description' => __( 'Order sort attribute ascending or descending.' ), 841 'type' => 'string', 842 'sanitize_callback' => 'sanitize_key', 843 'default' => 'asc', 844 'enum' => array( 845 'asc', 846 'desc', 847 ), 848 'validate_callback' => 'rest_validate_request_arg', 849 ); 850 $query_params['orderby'] = array( 851 'description' => __( 'Sort collection by resource attribute.' ), 852 'type' => 'string', 853 'sanitize_callback' => 'sanitize_key', 854 'default' => 'name', 855 'enum' => array( 856 'id', 857 'include', 858 'name', 859 'slug', 860 'term_group', 861 'description', 862 'count', 863 ), 864 'validate_callback' => 'rest_validate_request_arg', 865 ); 866 $query_params['hide_empty'] = array( 867 'description' => __( 'Whether to hide resources not assigned to any posts.' ), 868 'type' => 'boolean', 869 'default' => false, 870 'validate_callback' => 'rest_validate_request_arg', 871 ); 872 if ( $taxonomy->hierarchical ) { 873 $query_params['parent'] = array( 874 'description' => __( 'Limit result set to resources assigned to a specific parent.' ), 875 'type' => 'integer', 876 'sanitize_callback' => 'absint', 877 'validate_callback' => 'rest_validate_request_arg', 878 ); 879 } 880 $query_params['post'] = array( 881 'description' => __( 'Limit result set to resources assigned to a specific post.' ), 882 'type' => 'integer', 883 'default' => null, 884 'validate_callback' => 'rest_validate_request_arg', 885 ); 886 $query_params['slug'] = array( 887 'description' => __( 'Limit result set to resources with a specific slug.' ), 888 'type' => 'string', 889 'validate_callback' => 'rest_validate_request_arg', 890 ); 891 return $query_params; 892 } 893 894 /** 895 * Checks that the taxonomy is valid. 896 * 897 * @param string $taxonomy Taxonomy to check. 898 * @return WP_Error|boolean 899 */ 900 protected function check_is_taxonomy_allowed( $taxonomy ) { 901 $taxonomy_obj = get_taxonomy( $taxonomy ); 902 if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) { 903 return true; 904 } 905 return false; 906 } 907 } -
src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 /** 4 * Access users 5 */ 6 class WP_REST_Users_Controller extends WP_REST_Controller { 7 8 /** 9 * Instance of a user meta fields object. 10 * 11 * @access protected 12 * @var WP_REST_User_Meta_Fields 13 */ 14 protected $meta; 15 16 public function __construct() { 17 $this->namespace = 'wp/v2'; 18 $this->rest_base = 'users'; 19 20 $this->meta = new WP_REST_User_Meta_Fields(); 21 } 22 23 /** 24 * Register the routes for the objects of the controller. 25 */ 26 public function register_routes() { 27 28 register_rest_route( $this->namespace, '/' . $this->rest_base, array( 29 array( 30 'methods' => WP_REST_Server::READABLE, 31 'callback' => array( $this, 'get_items' ), 32 'permission_callback' => array( $this, 'get_items_permissions_check' ), 33 'args' => $this->get_collection_params(), 34 ), 35 array( 36 'methods' => WP_REST_Server::CREATABLE, 37 'callback' => array( $this, 'create_item' ), 38 'permission_callback' => array( $this, 'create_item_permissions_check' ), 39 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 40 ), 41 'schema' => array( $this, 'get_public_item_schema' ), 42 ) ); 43 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 44 array( 45 'methods' => WP_REST_Server::READABLE, 46 'callback' => array( $this, 'get_item' ), 47 'permission_callback' => array( $this, 'get_item_permissions_check' ), 48 'args' => array( 49 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 50 ), 51 ), 52 array( 53 'methods' => WP_REST_Server::EDITABLE, 54 'callback' => array( $this, 'update_item' ), 55 'permission_callback' => array( $this, 'update_item_permissions_check' ), 56 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 57 ), 58 array( 59 'methods' => WP_REST_Server::DELETABLE, 60 'callback' => array( $this, 'delete_item' ), 61 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 62 'args' => array( 63 'force' => array( 64 'default' => false, 65 'description' => __( 'Required to be true, as resource does not support trashing.' ), 66 ), 67 'reassign' => array(), 68 ), 69 ), 70 'schema' => array( $this, 'get_public_item_schema' ), 71 ) ); 72 73 register_rest_route( $this->namespace, '/' . $this->rest_base . '/me', array( 74 'methods' => WP_REST_Server::READABLE, 75 'callback' => array( $this, 'get_current_item' ), 76 'args' => array( 77 'context' => array(), 78 ), 79 'schema' => array( $this, 'get_public_item_schema' ), 80 )); 81 } 82 83 /** 84 * Permissions check for getting all users. 85 * 86 * @param WP_REST_Request $request Full details about the request. 87 * @return WP_Error|boolean 88 */ 89 public function get_items_permissions_check( $request ) { 90 // Check if roles is specified in GET request and if user can list users. 91 if ( ! empty( $request['roles'] ) && ! current_user_can( 'list_users' ) ) { 92 return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot filter by role.' ), array( 'status' => rest_authorization_required_code() ) ); 93 } 94 95 if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) { 96 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 97 } 98 99 if ( in_array( $request['orderby'], array( 'email', 'registered_date' ), true ) && ! current_user_can( 'list_users' ) ) { 100 return new WP_Error( 'rest_forbidden_orderby', __( 'Sorry, you cannot order by this parameter.' ), array( 'status' => rest_authorization_required_code() ) ); 101 } 102 103 return true; 104 } 105 106 /** 107 * Get all users 108 * 109 * @param WP_REST_Request $request Full details about the request. 110 * @return WP_Error|WP_REST_Response 111 */ 112 public function get_items( $request ) { 113 114 // Retrieve the list of registered collection query parameters. 115 $registered = $this->get_collection_params(); 116 117 // This array defines mappings between public API query parameters whose 118 // values are accepted as-passed, and their internal WP_Query parameter 119 // name equivalents (some are the same). Only values which are also 120 // present in $registered will be set. 121 $parameter_mappings = array( 122 'exclude' => 'exclude', 123 'include' => 'include', 124 'order' => 'order', 125 'per_page' => 'number', 126 'search' => 'search', 127 'roles' => 'role__in', 128 ); 129 130 $prepared_args = array(); 131 132 // For each known parameter which is both registered and present in the request, 133 // set the parameter's value on the query $prepared_args. 134 foreach ( $parameter_mappings as $api_param => $wp_param ) { 135 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { 136 $prepared_args[ $wp_param ] = $request[ $api_param ]; 137 } 138 } 139 140 if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) { 141 $prepared_args['offset'] = $request['offset']; 142 } else { 143 $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; 144 } 145 146 if ( isset( $registered['orderby'] ) ) { 147 $orderby_possibles = array( 148 'id' => 'ID', 149 'include' => 'include', 150 'name' => 'display_name', 151 'registered_date' => 'registered', 152 'slug' => 'user_nicename', 153 'email' => 'user_email', 154 'url' => 'user_url', 155 ); 156 $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ]; 157 } 158 159 if ( ! current_user_can( 'list_users' ) ) { 160 $prepared_args['has_published_posts'] = true; 161 } 162 163 if ( ! empty( $prepared_args['search'] ) ) { 164 $prepared_args['search'] = '*' . $prepared_args['search'] . '*'; 165 } 166 167 if ( isset( $registered['slug'] ) && ! empty( $request['slug'] ) ) { 168 $prepared_args['search'] = $request['slug']; 169 $prepared_args['search_columns'] = array( 'user_nicename' ); 170 } 171 172 /** 173 * Filter arguments, before passing to WP_User_Query, when querying users via the REST API. 174 * 175 * @see https://developer.wordpress.org/reference/classes/wp_user_query/ 176 * 177 * @param array $prepared_args Array of arguments for WP_User_Query. 178 * @param WP_REST_Request $request The current request. 179 */ 180 $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request ); 181 182 $query = new WP_User_Query( $prepared_args ); 183 184 $users = array(); 185 foreach ( $query->results as $user ) { 186 $data = $this->prepare_item_for_response( $user, $request ); 187 $users[] = $this->prepare_response_for_collection( $data ); 188 } 189 190 $response = rest_ensure_response( $users ); 191 192 // Store pagination values for headers then unset for count query. 193 $per_page = (int) $prepared_args['number']; 194 $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); 195 196 $prepared_args['fields'] = 'ID'; 197 198 $total_users = $query->get_total(); 199 if ( $total_users < 1 ) { 200 // Out-of-bounds, run the query again without LIMIT for total count 201 unset( $prepared_args['number'], $prepared_args['offset'] ); 202 $count_query = new WP_User_Query( $prepared_args ); 203 $total_users = $count_query->get_total(); 204 } 205 $response->header( 'X-WP-Total', (int) $total_users ); 206 $max_pages = ceil( $total_users / $per_page ); 207 $response->header( 'X-WP-TotalPages', (int) $max_pages ); 208 209 $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); 210 if ( $page > 1 ) { 211 $prev_page = $page - 1; 212 if ( $prev_page > $max_pages ) { 213 $prev_page = $max_pages; 214 } 215 $prev_link = add_query_arg( 'page', $prev_page, $base ); 216 $response->link_header( 'prev', $prev_link ); 217 } 218 if ( $max_pages > $page ) { 219 $next_page = $page + 1; 220 $next_link = add_query_arg( 'page', $next_page, $base ); 221 $response->link_header( 'next', $next_link ); 222 } 223 224 return $response; 225 } 226 227 /** 228 * Check if a given request has access to read a user 229 * 230 * @param WP_REST_Request $request Full details about the request. 231 * @return WP_Error|boolean 232 */ 233 public function get_item_permissions_check( $request ) { 234 235 $id = (int) $request['id']; 236 $user = get_userdata( $id ); 237 $types = get_post_types( array( 'show_in_rest' => true ), 'names' ); 238 239 if ( empty( $id ) || empty( $user->ID ) ) { 240 return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) ); 241 } 242 243 if ( get_current_user_id() === $id ) { 244 return true; 245 } 246 247 if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) { 248 return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) ); 249 } elseif ( ! count_user_posts( $id, $types ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) { 250 return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this resource.' ), array( 'status' => rest_authorization_required_code() ) ); 251 } 252 253 return true; 254 } 255 256 /** 257 * Get a single user 258 * 259 * @param WP_REST_Request $request Full details about the request. 260 * @return WP_Error|WP_REST_Response 261 */ 262 public function get_item( $request ) { 263 $id = (int) $request['id']; 264 $user = get_userdata( $id ); 265 266 if ( empty( $id ) || empty( $user->ID ) ) { 267 return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) ); 268 } 269 270 $user = $this->prepare_item_for_response( $user, $request ); 271 $response = rest_ensure_response( $user ); 272 273 return $response; 274 } 275 276 /** 277 * Get the current user 278 * 279 * @param WP_REST_Request $request Full details about the request. 280 * @return WP_Error|WP_REST_Response 281 */ 282 public function get_current_item( $request ) { 283 $current_user_id = get_current_user_id(); 284 if ( empty( $current_user_id ) ) { 285 return new WP_Error( 'rest_not_logged_in', __( 'You are not currently logged in.' ), array( 'status' => 401 ) ); 286 } 287 288 $user = wp_get_current_user(); 289 $response = $this->prepare_item_for_response( $user, $request ); 290 $response = rest_ensure_response( $response ); 291 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $current_user_id ) ) ); 292 $response->set_status( 302 ); 293 294 return $response; 295 } 296 297 /** 298 * Check if a given request has access create users 299 * 300 * @param WP_REST_Request $request Full details about the request. 301 * @return WP_Error|boolean 302 */ 303 public function create_item_permissions_check( $request ) { 304 305 if ( ! current_user_can( 'create_users' ) ) { 306 return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create resource.' ), array( 'status' => rest_authorization_required_code() ) ); 307 } 308 309 return true; 310 } 311 312 /** 313 * Create a single user 314 * 315 * @param WP_REST_Request $request Full details about the request. 316 * @return WP_Error|WP_REST_Response 317 */ 318 public function create_item( $request ) { 319 if ( ! empty( $request['id'] ) ) { 320 return new WP_Error( 'rest_user_exists', __( 'Cannot create existing resource.' ), array( 'status' => 400 ) ); 321 } 322 323 $schema = $this->get_item_schema(); 324 325 if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) { 326 $check_permission = $this->check_role_update( $request['id'], $request['roles'] ); 327 if ( is_wp_error( $check_permission ) ) { 328 return $check_permission; 329 } 330 } 331 332 $user = $this->prepare_item_for_database( $request ); 333 334 if ( is_multisite() ) { 335 $ret = wpmu_validate_user_signup( $user->user_login, $user->user_email ); 336 if ( is_wp_error( $ret['errors'] ) && ! empty( $ret['errors']->errors ) ) { 337 return $ret['errors']; 338 } 339 } 340 341 if ( is_multisite() ) { 342 $user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email ); 343 if ( ! $user_id ) { 344 return new WP_Error( 'rest_user_create', __( 'Error creating new resource.' ), array( 'status' => 500 ) ); 345 } 346 $user->ID = $user_id; 347 $user_id = wp_update_user( $user ); 348 if ( is_wp_error( $user_id ) ) { 349 return $user_id; 350 } 351 } else { 352 $user_id = wp_insert_user( $user ); 353 if ( is_wp_error( $user_id ) ) { 354 return $user_id; 355 } 356 } 357 358 $user = get_user_by( 'id', $user_id ); 359 if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) { 360 array_map( array( $user, 'add_role' ), $request['roles'] ); 361 } 362 363 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 364 $meta_update = $this->meta->update_value( $request['meta'], $user_id ); 365 if ( is_wp_error( $meta_update ) ) { 366 return $meta_update; 367 } 368 } 369 370 $fields_update = $this->update_additional_fields_for_object( $user, $request ); 371 if ( is_wp_error( $fields_update ) ) { 372 return $fields_update; 373 } 374 375 /** 376 * Fires after a user is created or updated via the REST API. 377 * 378 * @param WP_User $user Data used to create the user. 379 * @param WP_REST_Request $request Request object. 380 * @param boolean $creating True when creating user, false when updating user. 381 */ 382 do_action( 'rest_insert_user', $user, $request, true ); 383 384 $request->set_param( 'context', 'edit' ); 385 $response = $this->prepare_item_for_response( $user, $request ); 386 $response = rest_ensure_response( $response ); 387 $response->set_status( 201 ); 388 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user_id ) ) ); 389 390 return $response; 391 } 392 393 /** 394 * Check if a given request has access update a user 395 * 396 * @param WP_REST_Request $request Full details about the request. 397 * @return WP_Error|boolean 398 */ 399 public function update_item_permissions_check( $request ) { 400 401 $id = (int) $request['id']; 402 403 if ( ! current_user_can( 'edit_user', $id ) ) { 404 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit resource.' ), array( 'status' => rest_authorization_required_code() ) ); 405 } 406 407 if ( ! empty( $request['roles'] ) && ! current_user_can( 'edit_users' ) ) { 408 return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of this resource.' ), array( 'status' => rest_authorization_required_code() ) ); 409 } 410 411 return true; 412 } 413 414 /** 415 * Update a single user 416 * 417 * @param WP_REST_Request $request Full details about the request. 418 * @return WP_Error|WP_REST_Response 419 */ 420 public function update_item( $request ) { 421 $id = (int) $request['id']; 422 423 $user = get_userdata( $id ); 424 if ( ! $user ) { 425 return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) ); 426 } 427 428 if ( email_exists( $request['email'] ) && $request['email'] !== $user->user_email ) { 429 return new WP_Error( 'rest_user_invalid_email', __( 'Email address is invalid.' ), array( 'status' => 400 ) ); 430 } 431 432 if ( ! empty( $request['username'] ) && $request['username'] !== $user->user_login ) { 433 return new WP_Error( 'rest_user_invalid_argument', __( "Username isn't editable." ), array( 'status' => 400 ) ); 434 } 435 436 if ( ! empty( $request['slug'] ) && $request['slug'] !== $user->user_nicename && get_user_by( 'slug', $request['slug'] ) ) { 437 return new WP_Error( 'rest_user_invalid_slug', __( 'Slug is invalid.' ), array( 'status' => 400 ) ); 438 } 439 440 if ( ! empty( $request['roles'] ) ) { 441 $check_permission = $this->check_role_update( $id, $request['roles'] ); 442 if ( is_wp_error( $check_permission ) ) { 443 return $check_permission; 444 } 445 } 446 447 $user = $this->prepare_item_for_database( $request ); 448 449 // Ensure we're operating on the same user we already checked 450 $user->ID = $id; 451 452 $user_id = wp_update_user( $user ); 453 if ( is_wp_error( $user_id ) ) { 454 return $user_id; 455 } 456 457 $user = get_user_by( 'id', $id ); 458 if ( ! empty( $request['roles'] ) ) { 459 array_map( array( $user, 'add_role' ), $request['roles'] ); 460 } 461 462 $schema = $this->get_item_schema(); 463 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 464 $meta_update = $this->meta->update_value( $request['meta'], $id ); 465 if ( is_wp_error( $meta_update ) ) { 466 return $meta_update; 467 } 468 } 469 470 $fields_update = $this->update_additional_fields_for_object( $user, $request ); 471 if ( is_wp_error( $fields_update ) ) { 472 return $fields_update; 473 } 474 475 /* This action is documented in lib/endpoints/class-wp-rest-users-controller.php */ 476 do_action( 'rest_insert_user', $user, $request, false ); 477 478 $request->set_param( 'context', 'edit' ); 479 $response = $this->prepare_item_for_response( $user, $request ); 480 $response = rest_ensure_response( $response ); 481 return $response; 482 } 483 484 /** 485 * Check if a given request has access delete a user 486 * 487 * @param WP_REST_Request $request Full details about the request. 488 * @return WP_Error|boolean 489 */ 490 public function delete_item_permissions_check( $request ) { 491 492 $id = (int) $request['id']; 493 494 if ( ! current_user_can( 'delete_user', $id ) ) { 495 return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.' ), array( 'status' => rest_authorization_required_code() ) ); 496 } 497 498 return true; 499 } 500 501 /** 502 * Delete a single user 503 * 504 * @param WP_REST_Request $request Full details about the request. 505 * @return WP_Error|WP_REST_Response 506 */ 507 public function delete_item( $request ) { 508 $id = (int) $request['id']; 509 $reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null; 510 $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 511 512 // We don't support trashing for this type, error out 513 if ( ! $force ) { 514 return new WP_Error( 'rest_trash_not_supported', __( 'Users do not support trashing.' ), array( 'status' => 501 ) ); 515 } 516 517 $user = get_userdata( $id ); 518 if ( ! $user ) { 519 return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) ); 520 } 521 522 if ( ! empty( $reassign ) ) { 523 if ( $reassign === $id || ! get_userdata( $reassign ) ) { 524 return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid resource id for reassignment.' ), array( 'status' => 400 ) ); 525 } 526 } 527 528 $request->set_param( 'context', 'edit' ); 529 $response = $this->prepare_item_for_response( $user, $request ); 530 531 /** Include admin user functions to get access to wp_delete_user() */ 532 require_once ABSPATH . 'wp-admin/includes/user.php'; 533 534 $result = wp_delete_user( $id, $reassign ); 535 536 if ( ! $result ) { 537 return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) ); 538 } 539 540 /** 541 * Fires after a user is deleted via the REST API. 542 * 543 * @param WP_User $user The user data. 544 * @param WP_REST_Response $response The response returned from the API. 545 * @param WP_REST_Request $request The request sent to the API. 546 */ 547 do_action( 'rest_delete_user', $user, $response, $request ); 548 549 return $response; 550 } 551 552 /** 553 * Prepare a single user output for response 554 * 555 * @param object $user User object. 556 * @param WP_REST_Request $request Request object. 557 * @return WP_REST_Response $response Response data. 558 */ 559 public function prepare_item_for_response( $user, $request ) { 560 561 $data = array(); 562 $schema = $this->get_item_schema(); 563 if ( ! empty( $schema['properties']['id'] ) ) { 564 $data['id'] = $user->ID; 565 } 566 567 if ( ! empty( $schema['properties']['username'] ) ) { 568 $data['username'] = $user->user_login; 569 } 570 571 if ( ! empty( $schema['properties']['name'] ) ) { 572 $data['name'] = $user->display_name; 573 } 574 575 if ( ! empty( $schema['properties']['first_name'] ) ) { 576 $data['first_name'] = $user->first_name; 577 } 578 579 if ( ! empty( $schema['properties']['last_name'] ) ) { 580 $data['last_name'] = $user->last_name; 581 } 582 583 if ( ! empty( $schema['properties']['email'] ) ) { 584 $data['email'] = $user->user_email; 585 } 586 587 if ( ! empty( $schema['properties']['url'] ) ) { 588 $data['url'] = $user->user_url; 589 } 590 591 if ( ! empty( $schema['properties']['description'] ) ) { 592 $data['description'] = $user->description; 593 } 594 595 if ( ! empty( $schema['properties']['link'] ) ) { 596 $data['link'] = get_author_posts_url( $user->ID, $user->user_nicename ); 597 } 598 599 if ( ! empty( $schema['properties']['nickname'] ) ) { 600 $data['nickname'] = $user->nickname; 601 } 602 603 if ( ! empty( $schema['properties']['slug'] ) ) { 604 $data['slug'] = $user->user_nicename; 605 } 606 607 if ( ! empty( $schema['properties']['roles'] ) ) { 608 // Defensively call array_values() to ensure an array is returned. 609 $data['roles'] = array_values( $user->roles ); 610 } 611 612 if ( ! empty( $schema['properties']['registered_date'] ) ) { 613 $data['registered_date'] = date( 'c', strtotime( $user->user_registered ) ); 614 } 615 616 if ( ! empty( $schema['properties']['capabilities'] ) ) { 617 $data['capabilities'] = (object) $user->allcaps; 618 } 619 620 if ( ! empty( $schema['properties']['extra_capabilities'] ) ) { 621 $data['extra_capabilities'] = (object) $user->caps; 622 } 623 624 if ( ! empty( $schema['properties']['avatar_urls'] ) ) { 625 $data['avatar_urls'] = rest_get_avatar_urls( $user->user_email ); 626 } 627 628 if ( ! empty( $schema['properties']['meta'] ) ) { 629 $data['meta'] = $this->meta->get_value( $user->ID, $request ); 630 } 631 632 $context = ! empty( $request['context'] ) ? $request['context'] : 'embed'; 633 634 $data = $this->add_additional_fields_to_object( $data, $request ); 635 $data = $this->filter_response_by_context( $data, $context ); 636 637 // Wrap the data in a response object 638 $response = rest_ensure_response( $data ); 639 640 $response->add_links( $this->prepare_links( $user ) ); 641 642 /** 643 * Filter user data returned from the REST API. 644 * 645 * @param WP_REST_Response $response The response object. 646 * @param object $user User object used to create response. 647 * @param WP_REST_Request $request Request object. 648 */ 649 return apply_filters( 'rest_prepare_user', $response, $user, $request ); 650 } 651 652 /** 653 * Prepare links for the request. 654 * 655 * @param WP_Post $user User object. 656 * @return array Links for the given user. 657 */ 658 protected function prepare_links( $user ) { 659 $links = array( 660 'self' => array( 661 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user->ID ) ), 662 ), 663 'collection' => array( 664 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 665 ), 666 ); 667 668 return $links; 669 } 670 671 /** 672 * Prepare a single user for create or update 673 * 674 * @param WP_REST_Request $request Request object. 675 * @return object $prepared_user User object. 676 */ 677 protected function prepare_item_for_database( $request ) { 678 $prepared_user = new stdClass; 679 680 $schema = $this->get_item_schema(); 681 682 // required arguments. 683 if ( isset( $request['email'] ) && ! empty( $schema['properties']['email'] ) ) { 684 $prepared_user->user_email = $request['email']; 685 } 686 if ( isset( $request['username'] ) && ! empty( $schema['properties']['username'] ) ) { 687 $prepared_user->user_login = $request['username']; 688 } 689 if ( isset( $request['password'] ) && ! empty( $schema['properties']['password'] ) ) { 690 $prepared_user->user_pass = $request['password']; 691 } 692 693 // optional arguments. 694 if ( isset( $request['id'] ) ) { 695 $prepared_user->ID = absint( $request['id'] ); 696 } 697 if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) { 698 $prepared_user->display_name = $request['name']; 699 } 700 if ( isset( $request['first_name'] ) && ! empty( $schema['properties']['first_name'] ) ) { 701 $prepared_user->first_name = $request['first_name']; 702 } 703 if ( isset( $request['last_name'] ) && ! empty( $schema['properties']['last_name'] ) ) { 704 $prepared_user->last_name = $request['last_name']; 705 } 706 if ( isset( $request['nickname'] ) && ! empty( $schema['properties']['nickname'] ) ) { 707 $prepared_user->nickname = $request['nickname']; 708 } 709 if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) { 710 $prepared_user->user_nicename = $request['slug']; 711 } 712 if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) { 713 $prepared_user->description = $request['description']; 714 } 715 716 if ( isset( $request['url'] ) && ! empty( $schema['properties']['url'] ) ) { 717 $prepared_user->user_url = $request['url']; 718 } 719 720 // setting roles will be handled outside of this function. 721 if ( isset( $request['roles'] ) ) { 722 $prepared_user->role = false; 723 } 724 725 /** 726 * Filter user data before inserting user via the REST API. 727 * 728 * @param object $prepared_user User object. 729 * @param WP_REST_Request $request Request object. 730 */ 731 return apply_filters( 'rest_pre_insert_user', $prepared_user, $request ); 732 } 733 734 /** 735 * Determine if the current user is allowed to make the desired roles change. 736 * 737 * @param integer $user_id User ID. 738 * @param array $roles New user roles. 739 * @return WP_Error|boolean 740 */ 741 protected function check_role_update( $user_id, $roles ) { 742 global $wp_roles; 743 744 foreach ( $roles as $role ) { 745 746 if ( ! isset( $wp_roles->role_objects[ $role ] ) ) { 747 return new WP_Error( 'rest_user_invalid_role', sprintf( __( 'The role %s does not exist.' ), $role ), array( 'status' => 400 ) ); 748 } 749 750 $potential_role = $wp_roles->role_objects[ $role ]; 751 // Don't let anyone with 'edit_users' (admins) edit their own role to something without it. 752 // Multisite super admins can freely edit their blog roles -- they possess all caps. 753 if ( ! ( is_multisite() && current_user_can( 'manage_sites' ) ) && get_current_user_id() === $user_id && ! $potential_role->has_cap( 'edit_users' ) ) { 754 return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give resource that role.' ), array( 'status' => rest_authorization_required_code() ) ); 755 } 756 757 // The new role must be editable by the logged-in user. 758 759 /** Include admin functions to get access to get_editable_roles() */ 760 require_once ABSPATH . 'wp-admin/includes/admin.php'; 761 762 $editable_roles = get_editable_roles(); 763 if ( empty( $editable_roles[ $role ] ) ) { 764 return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give resource that role.' ), array( 'status' => 403 ) ); 765 } 766 } 767 768 return true; 769 770 } 771 772 /** 773 * Get the User's schema, conforming to JSON Schema 774 * 775 * @return array 776 */ 777 public function get_item_schema() { 778 $schema = array( 779 '$schema' => 'http://json-schema.org/draft-04/schema#', 780 'title' => 'user', 781 'type' => 'object', 782 'properties' => array( 783 'id' => array( 784 'description' => __( 'Unique identifier for the resource.' ), 785 'type' => 'integer', 786 'context' => array( 'embed', 'view', 'edit' ), 787 'readonly' => true, 788 ), 789 'username' => array( 790 'description' => __( 'Login name for the resource.' ), 791 'type' => 'string', 792 'context' => array( 'edit' ), 793 'required' => true, 794 'arg_options' => array( 795 'sanitize_callback' => 'sanitize_user', 796 ), 797 ), 798 'name' => array( 799 'description' => __( 'Display name for the resource.' ), 800 'type' => 'string', 801 'context' => array( 'embed', 'view', 'edit' ), 802 'arg_options' => array( 803 'sanitize_callback' => 'sanitize_text_field', 804 ), 805 ), 806 'first_name' => array( 807 'description' => __( 'First name for the resource.' ), 808 'type' => 'string', 809 'context' => array( 'edit' ), 810 'arg_options' => array( 811 'sanitize_callback' => 'sanitize_text_field', 812 ), 813 ), 814 'last_name' => array( 815 'description' => __( 'Last name for the resource.' ), 816 'type' => 'string', 817 'context' => array( 'edit' ), 818 'arg_options' => array( 819 'sanitize_callback' => 'sanitize_text_field', 820 ), 821 ), 822 'email' => array( 823 'description' => __( 'The email address for the resource.' ), 824 'type' => 'string', 825 'format' => 'email', 826 'context' => array( 'edit' ), 827 'required' => true, 828 ), 829 'url' => array( 830 'description' => __( 'URL of the resource.' ), 831 'type' => 'string', 832 'format' => 'uri', 833 'context' => array( 'embed', 'view', 'edit' ), 834 ), 835 'description' => array( 836 'description' => __( 'Description of the resource.' ), 837 'type' => 'string', 838 'context' => array( 'embed', 'view', 'edit' ), 839 'arg_options' => array( 840 'sanitize_callback' => 'wp_filter_post_kses', 841 ), 842 ), 843 'link' => array( 844 'description' => __( 'Author URL to the resource.' ), 845 'type' => 'string', 846 'format' => 'uri', 847 'context' => array( 'embed', 'view', 'edit' ), 848 'readonly' => true, 849 ), 850 'nickname' => array( 851 'description' => __( 'The nickname for the resource.' ), 852 'type' => 'string', 853 'context' => array( 'edit' ), 854 'arg_options' => array( 855 'sanitize_callback' => 'sanitize_text_field', 856 ), 857 ), 858 'slug' => array( 859 'description' => __( 'An alphanumeric identifier for the resource.' ), 860 'type' => 'string', 861 'context' => array( 'embed', 'view', 'edit' ), 862 'arg_options' => array( 863 'sanitize_callback' => array( $this, 'sanitize_slug' ), 864 ), 865 ), 866 'registered_date' => array( 867 'description' => __( 'Registration date for the resource.' ), 868 'type' => 'string', 869 'format' => 'date-time', 870 'context' => array( 'edit' ), 871 'readonly' => true, 872 ), 873 'roles' => array( 874 'description' => __( 'Roles assigned to the resource.' ), 875 'type' => 'array', 876 'context' => array( 'edit' ), 877 ), 878 'password' => array( 879 'description' => __( 'Password for the resource (never included).' ), 880 'type' => 'string', 881 'context' => array(), // Password is never displayed 882 'required' => true, 883 ), 884 'capabilities' => array( 885 'description' => __( 'All capabilities assigned to the resource.' ), 886 'type' => 'object', 887 'context' => array( 'edit' ), 888 'readonly' => true, 889 ), 890 'extra_capabilities' => array( 891 'description' => __( 'Any extra capabilities assigned to the resource.' ), 892 'type' => 'object', 893 'context' => array( 'edit' ), 894 'readonly' => true, 895 ), 896 ), 897 ); 898 899 if ( get_option( 'show_avatars' ) ) { 900 $avatar_properties = array(); 901 902 $avatar_sizes = rest_get_avatar_sizes(); 903 foreach ( $avatar_sizes as $size ) { 904 $avatar_properties[ $size ] = array( 905 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ), 906 'type' => 'string', 907 'format' => 'uri', 908 'context' => array( 'embed', 'view', 'edit' ), 909 ); 910 } 911 912 $schema['properties']['avatar_urls'] = array( 913 'description' => __( 'Avatar URLs for the resource.' ), 914 'type' => 'object', 915 'context' => array( 'embed', 'view', 'edit' ), 916 'readonly' => true, 917 'properties' => $avatar_properties, 918 ); 919 } 920 921 $schema['properties']['meta'] = $this->meta->get_field_schema(); 922 923 return $this->add_additional_fields_schema( $schema ); 924 } 925 926 /** 927 * Get the query params for collections 928 * 929 * @return array 930 */ 931 public function get_collection_params() { 932 $query_params = parent::get_collection_params(); 933 934 $query_params['context']['default'] = 'view'; 935 936 $query_params['exclude'] = array( 937 'description' => __( 'Ensure result set excludes specific ids.' ), 938 'type' => 'array', 939 'default' => array(), 940 'sanitize_callback' => 'wp_parse_id_list', 941 ); 942 $query_params['include'] = array( 943 'description' => __( 'Limit result set to specific ids.' ), 944 'type' => 'array', 945 'default' => array(), 946 'sanitize_callback' => 'wp_parse_id_list', 947 ); 948 $query_params['offset'] = array( 949 'description' => __( 'Offset the result set by a specific number of items.' ), 950 'type' => 'integer', 951 'sanitize_callback' => 'absint', 952 'validate_callback' => 'rest_validate_request_arg', 953 ); 954 $query_params['order'] = array( 955 'default' => 'asc', 956 'description' => __( 'Order sort attribute ascending or descending.' ), 957 'enum' => array( 'asc', 'desc' ), 958 'sanitize_callback' => 'sanitize_key', 959 'type' => 'string', 960 'validate_callback' => 'rest_validate_request_arg', 961 ); 962 $query_params['orderby'] = array( 963 'default' => 'name', 964 'description' => __( 'Sort collection by object attribute.' ), 965 'enum' => array( 966 'id', 967 'include', 968 'name', 969 'registered_date', 970 'slug', 971 'email', 972 'url', 973 ), 974 'sanitize_callback' => 'sanitize_key', 975 'type' => 'string', 976 'validate_callback' => 'rest_validate_request_arg', 977 ); 978 $query_params['slug'] = array( 979 'description' => __( 'Limit result set to resources with a specific slug.' ), 980 'type' => 'string', 981 'validate_callback' => 'rest_validate_request_arg', 982 ); 983 $query_params['roles'] = array( 984 'description' => __( 'Limit result set to resources matching at least one specific role provided. Accepts csv list or single role.' ), 985 'type' => 'array', 986 'sanitize_callback' => 'wp_parse_slug_list', 987 ); 988 return $query_params; 989 } 990 } -
src/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php
Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property
1 <?php 2 3 class WP_REST_Comment_Meta_Fields extends WP_REST_Meta_Fields { 4 /** 5 * Get the object type for meta. 6 * 7 * @return string 8 */ 9 protected function get_meta_type() { 10 return 'comment'; 11 } 12 13 /** 14 * Get the type for `register_rest_field`. 15 * 16 * @return string 17 */ 18 public function get_rest_field_type() { 19 return 'comment'; 20 } 21 } -
src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php
1 <?php 2 3 /** 4 * Manage meta values for an object. 5 */ 6 abstract class WP_REST_Meta_Fields { 7 8 /** 9 * Get the object type for meta. 10 * 11 * @return string One of 'post', 'comment', 'term', 'user', or anything 12 * else supported by `_get_meta_table()`. 13 */ 14 abstract protected function get_meta_type(); 15 16 /** 17 * Get the object type for `register_rest_field`. 18 * 19 * @return string Custom post type, 'taxonomy', 'comment', or `user`. 20 */ 21 abstract protected function get_rest_field_type(); 22 23 /** 24 * Register the meta field. 25 */ 26 public function register_field() { 27 register_rest_field( $this->get_rest_field_type(), 'meta', array( 28 'get_callback' => array( $this, 'get_value' ), 29 'update_callback' => array( $this, 'update_value' ), 30 'schema' => $this->get_field_schema(), 31 )); 32 } 33 34 /** 35 * Get the `meta` field value. 36 * 37 * @param int $object_id Object ID to fetch meta for. 38 * @param WP_REST_Request $request Full details about the request. 39 * @return WP_Error|object 40 */ 41 public function get_value( $object_id, $request ) { 42 $fields = $this->get_registered_fields(); 43 $response = array(); 44 45 foreach ( $fields as $name => $args ) { 46 $all_values = get_metadata( $this->get_meta_type(), $object_id, $name, false ); 47 if ( $args['single'] ) { 48 if ( empty( $all_values ) ) { 49 $value = $args['schema']['default']; 50 } else { 51 $value = $all_values[0]; 52 } 53 $value = $this->prepare_value_for_response( $value, $request, $args ); 54 } else { 55 $value = array(); 56 foreach ( $all_values as $row ) { 57 $value[] = $this->prepare_value_for_response( $row, $request, $args ); 58 } 59 } 60 61 $response[ $name ] = $value; 62 } 63 64 return (object) $response; 65 } 66 67 /** 68 * Prepare value for response. 69 * 70 * This is required because some native types cannot be stored correctly in 71 * the database, such as booleans. We need to cast back to the relevant 72 * type before passing back to JSON. 73 * 74 * @param mixed $value Value to prepare. 75 * @param WP_REST_Request $request Current request object. 76 * @param array $args Options for the field. 77 * @return mixed Prepared value. 78 */ 79 protected function prepare_value_for_response( $value, $request, $args ) { 80 if ( ! empty( $args['prepare_callback'] ) ) { 81 $value = call_user_func( $args['prepare_callback'], $value, $request, $args ); 82 } 83 84 return $value; 85 } 86 87 /** 88 * Update meta values. 89 * 90 * @param WP_REST_Request $request Full details about the request. 91 * @param int $object_id Object ID to fetch meta for. 92 * @return WP_Error|null Error if one occurs, null on success. 93 */ 94 public function update_value( $request, $object_id ) { 95 $fields = $this->get_registered_fields(); 96 97 foreach ( $fields as $name => $args ) { 98 if ( ! array_key_exists( $name, $request ) ) { 99 continue; 100 } 101 102 // A null value means reset the field, which is essentially deleting it 103 // from the database and then relying on the default value. 104 if ( is_null( $request[ $name ] ) ) { 105 $result = $this->delete_meta_value( $object_id, $name ); 106 } elseif ( $args['single'] ) { 107 $result = $this->update_meta_value( $object_id, $name, $request[ $name ] ); 108 } else { 109 $result = $this->update_multi_meta_value( $object_id, $name, $request[ $name ] ); 110 } 111 112 if ( is_wp_error( $result ) ) { 113 return $result; 114 } 115 } 116 117 return null; 118 } 119 120 /** 121 * Delete meta value for an object. 122 * 123 * @param int $object_id Object ID the field belongs to. 124 * @param string $name Key for the field. 125 * @return bool|WP_Error True if meta field is deleted, error otherwise. 126 */ 127 protected function delete_meta_value( $object_id, $name ) { 128 if ( ! current_user_can( 'delete_post_meta', $object_id, $name ) ) { 129 return new WP_Error( 130 'rest_cannot_delete', 131 sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ), 132 array( 'key' => $name, 'status' => rest_authorization_required_code() ) 133 ); 134 } 135 136 if ( ! delete_metadata( $this->get_meta_type(), $object_id, wp_slash( $name ) ) ) { 137 return new WP_Error( 138 'rest_meta_database_error', 139 __( 'Could not delete meta value from database.' ), 140 array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) 141 ); 142 } 143 144 return true; 145 } 146 147 /** 148 * Update multiple meta values for an object. 149 * 150 * Alters the list of values in the database to match the list of provided values. 151 * 152 * @param int $object_id Object ID to update. 153 * @param string $name Key for the custom field. 154 * @param array $values List of values to update to. 155 * @return bool|WP_Error True if meta fields are updated, error otherwise. 156 */ 157 protected function update_multi_meta_value( $object_id, $name, $values ) { 158 if ( ! current_user_can( 'edit_post_meta', $object_id, $name ) ) { 159 return new WP_Error( 160 'rest_cannot_update', 161 sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ), 162 array( 'key' => $name, 'status' => rest_authorization_required_code() ) 163 ); 164 } 165 166 $current = get_metadata( $this->get_meta_type(), $object_id, $name, false ); 167 168 $to_remove = $current; 169 $to_add = $values; 170 foreach ( $to_add as $add_key => $value ) { 171 $remove_keys = array_keys( $to_remove, $value, true ); 172 if ( empty( $remove_keys ) ) { 173 continue; 174 } 175 176 if ( count( $remove_keys ) > 1 ) { 177 // To remove, we need to remove first, then add, so don't touch. 178 continue; 179 } 180 181 $remove_key = $remove_keys[0]; 182 unset( $to_remove[ $remove_key ] ); 183 unset( $to_add[ $add_key ] ); 184 } 185 186 // `delete_metadata` removes _all_ instances of the value, so only call 187 // once. 188 $to_remove = array_unique( $to_remove ); 189 foreach ( $to_remove as $value ) { 190 if ( ! delete_metadata( $this->get_meta_type(), $object_id, wp_slash( $name ), wp_slash( $value ) ) ) { 191 return new WP_Error( 192 'rest_meta_database_error', 193 __( 'Could not update meta value in database.' ), 194 array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) 195 ); 196 } 197 } 198 foreach ( $to_add as $value ) { 199 if ( ! add_metadata( $this->get_meta_type(), $object_id, wp_slash( $name ), wp_slash( $value ) ) ) { 200 return new WP_Error( 201 'rest_meta_database_error', 202 __( 'Could not update meta value in database.' ), 203 array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) 204 ); 205 } 206 } 207 208 return true; 209 } 210 211 /** 212 * Update meta value for an object. 213 * 214 * @param int $object_id Object ID to update. 215 * @param string $name Key for the custom field. 216 * @param mixed $value Updated value. 217 * @return bool|WP_Error True if meta field is updated, error otherwise. 218 */ 219 protected function update_meta_value( $object_id, $name, $value ) { 220 if ( ! current_user_can( 'edit_post_meta', $object_id, $name ) ) { 221 return new WP_Error( 222 'rest_cannot_update', 223 sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ), 224 array( 'key' => $name, 'status' => rest_authorization_required_code() ) 225 ); 226 } 227 228 $meta_type = $this->get_meta_type(); 229 $meta_key = wp_slash( $name ); 230 $meta_value = wp_slash( $value ); 231 232 // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false. 233 $old_value = get_metadata( $meta_type, $object_id, $meta_key ); 234 if ( 1 === count( $old_value ) ) { 235 if ( $old_value[0] === $meta_value ) { 236 return true; 237 } 238 } 239 240 if ( ! update_metadata( $meta_type, $object_id, $meta_key, $meta_value ) ) { 241 return new WP_Error( 242 'rest_meta_database_error', 243 __( 'Could not update meta value in database.' ), 244 array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) 245 ); 246 } 247 248 return true; 249 } 250 251 /** 252 * Get all the registered meta fields. 253 * 254 * @return array 255 */ 256 protected function get_registered_fields() { 257 $registered = array(); 258 259 foreach ( get_registered_meta_keys( $this->get_meta_type() ) as $name => $args ) { 260 if ( empty( $args['show_in_rest'] ) ) { 261 continue; 262 } 263 264 $rest_args = array(); 265 if ( is_array( $args['show_in_rest'] ) ) { 266 $rest_args = $args['show_in_rest']; 267 } 268 269 $default_args = array( 270 'name' => $name, 271 'single' => $args['single'], 272 'schema' => array(), 273 'prepare_callback' => array( $this, 'prepare_value' ), 274 ); 275 $default_schema = array( 276 'type' => null, 277 'description' => empty( $args['description'] ) ? '' : $args['description'], 278 'default' => isset( $args['default'] ) ? $args['default'] : null, 279 ); 280 $rest_args = array_merge( $default_args, $rest_args ); 281 $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); 282 283 if ( empty( $rest_args['schema']['type'] ) ) { 284 // Skip over meta fields that don't have a defined type. 285 if ( empty( $args['type'] ) ) { 286 continue; 287 } 288 289 if ( $rest_args['single'] ) { 290 $rest_args['schema']['type'] = $args['type']; 291 } else { 292 $rest_args['schema']['type'] = 'array'; 293 $rest_args['schema']['items'] = array( 294 'type' => $args['type'], 295 ); 296 } 297 } 298 299 $registered[ $rest_args['name'] ] = $rest_args; 300 } // End foreach(). 301 302 return $registered; 303 } 304 305 /** 306 * Get the object's `meta` schema, conforming to JSON Schema. 307 * 308 * @return array 309 */ 310 public function get_field_schema() { 311 $fields = $this->get_registered_fields(); 312 313 $schema = array( 314 'description' => __( 'Meta fields.' ), 315 'type' => 'object', 316 'context' => array( 'view', 'edit' ), 317 'properties' => array(), 318 ); 319 320 foreach ( $fields as $key => $args ) { 321 $schema['properties'][ $key ] = $args['schema']; 322 } 323 324 return $schema; 325 } 326 327 /** 328 * Prepare a meta value for output. 329 * 330 * Default preparation for meta fields. Override by passing the 331 * `prepare_callback` in your `show_in_rest` options. 332 * 333 * @param mixed $value Meta value from the database. 334 * @param WP_REST_Request $request Request object. 335 * @param array $args REST-specific options for the meta key. 336 * @return mixed Value prepared for output. 337 */ 338 public static function prepare_value( $value, $request, $args ) { 339 $type = $args['schema']['type']; 340 341 // For multi-value fields, check the item type instead. 342 if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) { 343 $type = $args['schema']['items']['type']; 344 } 345 346 switch ( $type ) { 347 case 'string': 348 $value = (string) $value; 349 break; 350 case 'number': 351 $value = (float) $value; 352 break; 353 case 'boolean': 354 $value = (bool) $value; 355 break; 356 } 357 358 // Don't allow objects to be output. 359 if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) { 360 return null; 361 } 362 363 return $value; 364 } 365 } -
src/wp-includes/rest-api/fields/class-wp-rest-post-meta-fields.php
1 <?php 2 3 class WP_REST_Post_Meta_Fields extends WP_REST_Meta_Fields { 4 /** 5 * Post type to register fields for. 6 * 7 * @var string 8 */ 9 protected $post_type; 10 11 /** 12 * Constructor. 13 * 14 * @param string $post_type Post type to register fields for. 15 */ 16 public function __construct( $post_type ) { 17 $this->post_type = $post_type; 18 } 19 20 /** 21 * Get the object type for meta. 22 * 23 * @return string 24 */ 25 protected function get_meta_type() { 26 return 'post'; 27 } 28 29 /** 30 * Get the type for `register_rest_field`. 31 * 32 * @return string Custom post type slug. 33 */ 34 public function get_rest_field_type() { 35 return $this->post_type; 36 } 37 } -
src/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php
1 <?php 2 3 /** 4 * Manage meta values for terms. 5 */ 6 class WP_REST_Term_Meta_Fields extends WP_REST_Meta_Fields { 7 /** 8 * Taxonomy to register fields for. 9 * 10 * @var string 11 */ 12 protected $taxonomy; 13 /** 14 * Constructor. 15 * 16 * @param string $taxonomy Taxonomy to register fields for. 17 */ 18 public function __construct( $taxonomy ) { 19 $this->taxonomy = $taxonomy; 20 } 21 22 /** 23 * Get the object type for meta. 24 * 25 * @return string 26 */ 27 protected function get_meta_type() { 28 return 'term'; 29 } 30 31 /** 32 * Get the type for `register_rest_field`. 33 * 34 * @return string 35 */ 36 public function get_rest_field_type() { 37 return 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy; 38 } 39 } -
src/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php
1 <?php 2 3 class WP_REST_User_Meta_Fields extends WP_REST_Meta_Fields { 4 /** 5 * Get the object type for meta. 6 * 7 * @return string 8 */ 9 protected function get_meta_type() { 10 return 'user'; 11 } 12 13 /** 14 * Get the type for `register_rest_field`. 15 * 16 * @return string 17 */ 18 public function get_rest_field_type() { 19 return 'user'; 20 } 21 } -
src/wp-includes/rest-api.php
71 71 } 72 72 73 73 /** 74 * Registers a new field on an existing WordPress object type. 75 * 76 * @since 4.7.0 77 * 78 * @global array $wp_rest_additional_fields Holds registered fields, organized 79 * by object type. 80 * 81 * @param string|array $object_type Object(s) the field is being registered 82 * to, "post"|"term"|"comment" etc. 83 * @param string $attribute The attribute name. 84 * @param array $args { 85 * Optional. An array of arguments used to handle the registered field. 86 * 87 * @type string|array|null $get_callback Optional. The callback function used to retrieve the field 88 * value. Default is 'null', the field will not be returned in 89 * the response. 90 * @type string|array|null $update_callback Optional. The callback function used to set and update the 91 * field value. Default is 'null', the value cannot be set or 92 * updated. 93 * @type string|array|null $schema Optional. The callback function used to create the schema for 94 * this field. Default is 'null', no schema entry will be returned. 95 * } 96 */ 97 function register_rest_field( $object_type, $attribute, $args = array() ) { 98 $defaults = array( 99 'get_callback' => null, 100 'update_callback' => null, 101 'schema' => null, 102 ); 103 104 $args = wp_parse_args( $args, $defaults ); 105 106 global $wp_rest_additional_fields; 107 108 $object_types = (array) $object_type; 109 110 foreach ( $object_types as $object_type ) { 111 $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args; 112 } 113 } 114 115 /** 74 116 * Registers rewrite rules for the API. 75 117 * 76 118 * @since 4.4.0 … … 125 167 } 126 168 127 169 /** 170 * Registers default REST API routes. 171 * 172 * @since 4.7.0 173 */ 174 function create_initial_rest_routes() { 175 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { 176 $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller'; 177 178 if ( ! class_exists( $class ) ) { 179 continue; 180 } 181 $controller = new $class( $post_type->name ); 182 if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) { 183 continue; 184 } 185 186 $controller->register_routes(); 187 188 if ( post_type_supports( $post_type->name, 'revisions' ) ) { 189 $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name ); 190 $revisions_controller->register_routes(); 191 } 192 } 193 194 // Post types. 195 $controller = new WP_REST_Post_Types_Controller; 196 $controller->register_routes(); 197 198 // Post statuses. 199 $controller = new WP_REST_Post_Statuses_Controller; 200 $controller->register_routes(); 201 202 // Taxonomies. 203 $controller = new WP_REST_Taxonomies_Controller; 204 $controller->register_routes(); 205 206 // Terms. 207 foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) { 208 $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller'; 209 210 if ( ! class_exists( $class ) ) { 211 continue; 212 } 213 $controller = new $class( $taxonomy->name ); 214 if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) { 215 continue; 216 } 217 218 $controller->register_routes(); 219 } 220 221 // Users. 222 $controller = new WP_REST_Users_Controller; 223 $controller->register_routes(); 224 225 // Comments. 226 $controller = new WP_REST_Comments_Controller; 227 $controller->register_routes(); 228 229 // Settings. 230 $controller = new WP_REST_Settings_Controller; 231 $controller->register_routes(); 232 } 233 234 /** 128 235 * Loads the REST API. 129 236 * 130 237 * @since 4.4.0 … … 683 790 684 791 return array( $local, $utc ); 685 792 } 793 794 /** 795 * Returns a contextual HTTP error code for authorization failure. 796 * 797 * @since 4.7.0 798 * 799 * @return integer 401 if the user is not logged in, 403 if the user is logged in. 800 */ 801 function rest_authorization_required_code() { 802 return is_user_logged_in() ? 403 : 401; 803 } 804 805 /** 806 * Validate a request argument based on details registered to the route. 807 * 808 * @since 4.7.0 809 * 810 * @param mixed $value 811 * @param WP_REST_Request $request 812 * @param string $param 813 * @return WP_Error|boolean 814 */ 815 function rest_validate_request_arg( $value, $request, $param ) { 816 $attributes = $request->get_attributes(); 817 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { 818 return true; 819 } 820 $args = $attributes['args'][ $param ]; 821 822 if ( ! empty( $args['enum'] ) ) { 823 if ( ! in_array( $value, $args['enum'] ) ) { 824 return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: list of valid values */ __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) ); 825 } 826 } 827 828 if ( 'integer' === $args['type'] && ! is_numeric( $value ) ) { 829 return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'integer' ) ); 830 } 831 832 if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) { 833 return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) ); 834 } 835 836 if ( 'string' === $args['type'] && ! is_string( $value ) ) { 837 return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'string' ) ); 838 } 839 840 if ( isset( $args['format'] ) ) { 841 switch ( $args['format'] ) { 842 case 'date-time' : 843 if ( ! rest_parse_date( $value ) ) { 844 return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) ); 845 } 846 break; 847 848 case 'email' : 849 if ( ! is_email( $value ) ) { 850 return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) ); 851 } 852 break; 853 case 'ipv4' : 854 if ( ! rest_is_ip_address( $value ) ) { 855 return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) ); 856 } 857 break; 858 } 859 } 860 861 if ( in_array( $args['type'], array( 'numeric', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) { 862 if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) { 863 if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) { 864 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (exclusive)' ), $param, $args['minimum'] ) ); 865 } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { 866 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (inclusive)' ), $param, $args['minimum'] ) ); 867 } 868 } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { 869 if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { 870 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (exclusive)' ), $param, $args['maximum'] ) ); 871 } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { 872 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (inclusive)' ), $param, $args['maximum'] ) ); 873 } 874 } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) { 875 if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { 876 if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { 877 return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); 878 } 879 } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { 880 if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { 881 return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); 882 } 883 } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { 884 if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { 885 return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); 886 } 887 } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { 888 if ( $value > $args['maximum'] || $value < $args['minimum'] ) { 889 return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); 890 } 891 } 892 } 893 } 894 895 return true; 896 } 897 898 /** 899 * Sanitize a request argument based on details registered to the route. 900 * 901 * @since 4.7.0 902 * 903 * @param mixed $value 904 * @param WP_REST_Request $request 905 * @param string $param 906 * @return mixed 907 */ 908 function rest_sanitize_request_arg( $value, $request, $param ) { 909 $attributes = $request->get_attributes(); 910 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { 911 return $value; 912 } 913 $args = $attributes['args'][ $param ]; 914 915 if ( 'integer' === $args['type'] ) { 916 return (int) $value; 917 } 918 919 if ( 'boolean' === $args['type'] ) { 920 return rest_sanitize_boolean( $value ); 921 } 922 923 if ( isset( $args['format'] ) ) { 924 switch ( $args['format'] ) { 925 case 'date-time' : 926 return sanitize_text_field( $value ); 927 928 case 'email' : 929 /* 930 * sanitize_email() validates, which would be unexpected 931 */ 932 return sanitize_text_field( $value ); 933 934 case 'uri' : 935 return esc_url_raw( $value ); 936 937 case 'ipv4' : 938 return sanitize_text_field( $value ); 939 } 940 } 941 942 return $value; 943 } 944 945 /** 946 * Parse a request argument based on details registered to the route. 947 * 948 * Runs a validation check and sanitizes the value, primarily to be used via 949 * the `sanitize_callback` arguments in the endpoint args registration. 950 * 951 * @since 4.7.0 952 * 953 * @param mixed $value 954 * @param WP_REST_Request $request 955 * @param string $param 956 * @return mixed 957 */ 958 function rest_parse_request_arg( $value, $request, $param ) { 959 $is_valid = rest_validate_request_arg( $value, $request, $param ); 960 961 if ( is_wp_error( $is_valid ) ) { 962 return $is_valid; 963 } 964 965 $value = rest_sanitize_request_arg( $value, $request, $param ); 966 967 return $value; 968 } 969 970 /** 971 * Determines if a IPv4 address is valid. 972 * 973 * Does not handle IPv6 addresses. 974 * 975 * @since 4.7.0 976 * 977 * @param string $ipv4 IP 32-bit address. 978 * @return string|false The valid IPv4 address, otherwise false. 979 */ 980 function rest_is_ip_address( $ipv4 ) { 981 $pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/'; 982 983 if ( ! preg_match( $pattern, $ipv4 ) ) { 984 return false; 985 } 986 987 return $ipv4; 988 } 989 990 /** 991 * Changes a boolean-like value into the proper boolean value. 992 * 993 * @since 4.7.0 994 * 995 * @param bool|string|int $value The value being evaluated. 996 * @return boolean Returns the proper associated boolean value. 997 */ 998 function rest_sanitize_boolean( $value ) { 999 // String values are translated to `true`; make sure 'false' is false. 1000 if ( is_string( $value ) ) { 1001 $value = strtolower( $value ); 1002 if ( in_array( $value, array( 'false', '0' ), true ) ) { 1003 $value = false; 1004 } 1005 } 1006 1007 // Everything else will map nicely to boolean. 1008 return (boolean) $value; 1009 } 1010 1011 /** 1012 * Determines if a given value is boolean-like. 1013 * 1014 * @since 4.7.0 1015 * 1016 * @param bool|string $maybe_bool The value being evaluated. 1017 * @return boolean True if a boolean, otherwise false. 1018 */ 1019 function rest_is_boolean( $maybe_bool ) { 1020 if ( is_bool( $maybe_bool ) ) { 1021 return true; 1022 } 1023 1024 if ( is_string( $maybe_bool ) ) { 1025 $maybe_bool = strtolower( $maybe_bool ); 1026 1027 $valid_boolean_values = array( 1028 'false', 1029 'true', 1030 '0', 1031 '1', 1032 ); 1033 1034 return in_array( $maybe_bool, $valid_boolean_values, true ); 1035 } 1036 1037 if ( is_int( $maybe_bool ) ) { 1038 return in_array( $maybe_bool, array( 0, 1 ), true ); 1039 } 1040 1041 return false; 1042 } 1043 1044 /** 1045 * Retrieves the avatar urls in various sizes based on a given email address. 1046 * 1047 * @since 4.7.0 1048 * 1049 * @see get_avatar_url() 1050 * 1051 * @param string $email Email address. 1052 * @return array $urls Gravatar url for each size. 1053 */ 1054 function rest_get_avatar_urls( $email ) { 1055 $avatar_sizes = rest_get_avatar_sizes(); 1056 1057 $urls = array(); 1058 foreach ( $avatar_sizes as $size ) { 1059 $urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) ); 1060 } 1061 1062 return $urls; 1063 } 1064 1065 /** 1066 * Retrieves the pixel sizes for avatars. 1067 * 1068 * @since 4.7.0 1069 * 1070 * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`. 1071 */ 1072 function rest_get_avatar_sizes() { 1073 /** 1074 * Filter the REST avatar sizes. 1075 * 1076 * Use this filter to adjust the array of sizes returned by the 1077 * `rest_get_avatar_sizes` function. 1078 * 1079 * @since 4.4.0 1080 * 1081 * @param array $sizes An array of int values that are the pixel sizes for avatars. 1082 * Default `[ 24, 48, 96 ]`. 1083 */ 1084 return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) ); 1085 } -
src/wp-includes/script-loader.php
499 499 $scripts->add( 'media-audiovideo', "/wp-includes/js/media-audiovideo$suffix.js", array( 'media-editor' ), false, 1 ); 500 500 $scripts->add( 'mce-view', "/wp-includes/js/mce-view$suffix.js", array( 'shortcode', 'jquery', 'media-views', 'media-audiovideo' ), false, 1 ); 501 501 502 $scripts->add( 'wp-api', "/wp-includes/js/wp-api$suffix.js", array( 'jquery', 'backbone', 'underscore' ), false, 1 ); 503 did_action( 'init' ) && $scripts->localize( 'wp-api', 'wpApiSettings', array( 504 'root' => esc_url_raw( get_rest_url() ), 505 'nonce' => wp_create_nonce( 'wp_rest' ), 506 'versionString' => 'wp/v2/', 507 ) ); 508 502 509 if ( is_admin() ) { 503 510 $scripts->add( 'admin-tags', "/wp-admin/js/tags$suffix.js", array( 'jquery', 'wp-ajax-response' ), false, 1 ); 504 511 did_action( 'init' ) && $scripts->localize( 'admin-tags', 'tagsl10n', array( -
src/wp-includes/taxonomy.php
67 67 'delete_terms' => 'delete_categories', 68 68 'assign_terms' => 'assign_categories', 69 69 ), 70 'show_in_rest' => true, 71 'rest_base' => 'categories', 72 'rest_controller_class' => 'WP_REST_Terms_Controller', 70 73 ) ); 71 74 72 75 register_taxonomy( 'post_tag', 'post', array( … … 83 86 'delete_terms' => 'delete_post_tags', 84 87 'assign_terms' => 'assign_post_tags', 85 88 ), 89 'show_in_rest' => true, 90 'rest_base' => 'tags', 91 'rest_controller_class' => 'WP_REST_Terms_Controller', 86 92 ) ); 87 93 88 94 register_taxonomy( 'nav_menu', 'nav_menu_item', array( -
src/wp-settings.php
218 218 require( ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php' ); 219 219 require( ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php' ); 220 220 require( ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php' ); 221 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-controller.php' ); 222 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-posts-controller.php' ); 223 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-controller.php' ); 224 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php' ); 225 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php' ); 226 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php' ); 227 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-taxonomies-controller.php' ); 228 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.php' ); 229 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' ); 230 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-comments-controller.php' ); 231 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller.php' ); 232 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php' ); 233 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php' ); 234 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' ); 235 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-term-meta-fields.php' ); 236 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-user-meta-fields.php' ); 221 237 222 238 $GLOBALS['wp_embed'] = new WP_Embed(); 223 239 -
tests/phpunit/includes/bootstrap.php
40 40 define( 'WP_MEMORY_LIMIT', -1 ); 41 41 define( 'WP_MAX_MEMORY_LIMIT', -1 ); 42 42 43 define( 'REST_TESTS_IMPOSSIBLY_HIGH_NUMBER', 99999999 ); 44 43 45 $PHP_SELF = $GLOBALS['PHP_SELF'] = $_SERVER['PHP_SELF'] = '/index.php'; 44 46 45 47 // Should we run in multisite mode? … … 88 90 89 91 require dirname( __FILE__ ) . '/testcase.php'; 90 92 require dirname( __FILE__ ) . '/testcase-rest-api.php'; 93 require dirname( __FILE__ ) . '/testcase-rest-controller.php'; 94 require dirname( __FILE__ ) . '/testcase-rest-post-type-controller.php'; 91 95 require dirname( __FILE__ ) . '/testcase-xmlrpc.php'; 92 96 require dirname( __FILE__ ) . '/testcase-ajax.php'; 93 97 require dirname( __FILE__ ) . '/testcase-canonical.php'; -
tests/phpunit/includes/testcase-rest-controller.php
1 <?php 2 3 abstract class WP_Test_REST_Controller_Testcase extends WP_Test_REST_TestCase { 4 5 protected $server; 6 7 public function setUp() { 8 parent::setUp(); 9 add_filter( 'rest_url', array( $this, 'filter_rest_url_for_leading_slash' ), 10, 2 ); 10 /** @var WP_REST_Server $wp_rest_server */ 11 global $wp_rest_server; 12 $this->server = $wp_rest_server = new WP_Test_Spy_REST_Server; 13 do_action( 'rest_api_init' ); 14 } 15 16 public function tearDown() { 17 parent::tearDown(); 18 remove_filter( 'rest_url', array( $this, 'test_rest_url_for_leading_slash' ), 10, 2 ); 19 /** @var WP_REST_Server $wp_rest_server */ 20 global $wp_rest_server; 21 $wp_rest_server = null; 22 } 23 24 abstract public function test_register_routes(); 25 26 abstract public function test_context_param(); 27 28 abstract public function test_get_items(); 29 30 abstract public function test_get_item(); 31 32 abstract public function test_create_item(); 33 34 abstract public function test_update_item(); 35 36 abstract public function test_delete_item(); 37 38 abstract public function test_prepare_item(); 39 40 abstract public function test_get_item_schema(); 41 42 public function filter_rest_url_for_leading_slash( $url, $path ) { 43 // Make sure path for rest_url has a leading slash for proper resolution. 44 $this->assertTrue( 0 === strpos( $path, '/' ) ); 45 46 return $url; 47 } 48 } -
tests/phpunit/includes/testcase-rest-post-type-controller.php
1 <?php 2 3 abstract class WP_Test_REST_Post_Type_Controller_Testcase extends WP_Test_REST_Controller_Testcase { 4 5 protected function check_post_data( $post, $data, $context, $links ) { 6 $post_type_obj = get_post_type_object( $post->post_type ); 7 8 // Standard fields 9 $this->assertEquals( $post->ID, $data['id'] ); 10 $this->assertEquals( $post->post_name, $data['slug'] ); 11 $this->assertEquals( get_permalink( $post->ID ), $data['link'] ); 12 if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) { 13 $this->assertNull( $data['date_gmt'] ); 14 } 15 $this->assertEquals( mysql_to_rfc3339( $post->post_date ), $data['date'] ); 16 17 if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) { 18 $this->assertNull( $data['modified_gmt'] ); 19 } 20 $this->assertEquals( mysql_to_rfc3339( $post->post_modified ), $data['modified'] ); 21 22 // author 23 if ( post_type_supports( $post->post_type, 'author' ) ) { 24 $this->assertEquals( $post->post_author, $data['author'] ); 25 } else { 26 $this->assertEmpty( $data['author'] ); 27 } 28 29 // post_parent 30 if ( $post_type_obj->hierarchical ) { 31 $this->assertArrayHasKey( 'parent', $data ); 32 if ( $post->post_parent ) { 33 if ( is_int( $data['parent'] ) ) { 34 $this->assertEquals( $post->post_parent, $data['parent'] ); 35 } else { 36 $this->assertEquals( $post->post_parent, $data['parent']['id'] ); 37 $this->check_get_post_response( $data['parent'], get_post( $data['parent']['id'] ), 'view-parent' ); 38 } 39 } else { 40 $this->assertEmpty( $data['parent'] ); 41 } 42 } else { 43 $this->assertFalse( isset( $data['parent'] ) ); 44 } 45 46 // page attributes 47 if ( $post_type_obj->hierarchical && post_type_supports( $post->post_type, 'page-attributes' ) ) { 48 $this->assertEquals( $post->menu_order, $data['menu_order'] ); 49 } else { 50 $this->assertFalse( isset( $data['menu_order'] ) ); 51 } 52 53 // Comments 54 if ( post_type_supports( $post->post_type, 'comments' ) ) { 55 $this->assertEquals( $post->comment_status, $data['comment_status'] ); 56 $this->assertEquals( $post->ping_status, $data['ping_status'] ); 57 } else { 58 $this->assertFalse( isset( $data['comment_status'] ) ); 59 $this->assertFalse( isset( $data['ping_status'] ) ); 60 } 61 62 if ( 'post' === $post->post_type ) { 63 $this->assertEquals( is_sticky( $post->ID ), $data['sticky'] ); 64 } 65 66 if ( 'post' === $post->post_type && 'edit' === $context ) { 67 $this->assertEquals( $post->post_password, $data['password'] ); 68 } 69 70 if ( 'page' === $post->post_type ) { 71 $this->assertEquals( get_page_template_slug( $post->ID ), $data['template'] ); 72 } 73 74 if ( post_type_supports( $post->post_type, 'thumbnail' ) ) { 75 $this->assertEquals( (int) get_post_thumbnail_id( $post->ID ), $data['featured_media'] ); 76 } else { 77 $this->assertFalse( isset( $data['featured_media'] ) ); 78 } 79 80 // Check post format. 81 if ( post_type_supports( $post->post_type, 'post-formats' ) ) { 82 $post_format = get_post_format( $post->ID ); 83 if ( empty( $post_format ) ) { 84 $this->assertEquals( 'standard', $data['format'] ); 85 } else { 86 $this->assertEquals( get_post_format( $post->ID ), $data['format'] ); 87 } 88 } else { 89 $this->assertFalse( isset( $data['format'] ) ); 90 } 91 92 // Check filtered values. 93 if ( post_type_supports( $post->post_type, 'title' ) ) { 94 add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 95 $this->assertEquals( get_the_title( $post->ID ), $data['title']['rendered'] ); 96 remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 97 if ( 'edit' === $context ) { 98 $this->assertEquals( $post->post_title, $data['title']['raw'] ); 99 } else { 100 $this->assertFalse( isset( $data['title']['raw'] ) ); 101 } 102 } else { 103 $this->assertFalse( isset( $data['title'] ) ); 104 } 105 106 if ( post_type_supports( $post->post_type, 'editor' ) ) { 107 // TODO: apply content filter for more accurate testing. 108 if ( ! $post->post_password ) { 109 $this->assertEquals( wpautop( $post->post_content ), $data['content']['rendered'] ); 110 } 111 112 if ( 'edit' === $context ) { 113 $this->assertEquals( $post->post_content, $data['content']['raw'] ); 114 } else { 115 $this->assertFalse( isset( $data['content']['raw'] ) ); 116 } 117 } else { 118 $this->assertFalse( isset( $data['content'] ) ); 119 } 120 121 if ( post_type_supports( $post->post_type, 'excerpt' ) ) { 122 if ( empty( $post->post_password ) ) { 123 // TODO: apply excerpt filter for more accurate testing. 124 $this->assertEquals( wpautop( $post->post_excerpt ), $data['excerpt']['rendered'] ); 125 } else { 126 // TODO: better testing for excerpts for password protected posts. 127 } 128 if ( 'edit' === $context ) { 129 $this->assertEquals( $post->post_excerpt, $data['excerpt']['raw'] ); 130 } else { 131 $this->assertFalse( isset( $data['excerpt']['raw'] ) ); 132 } 133 } else { 134 $this->assertFalse( isset( $data['excerpt'] ) ); 135 } 136 137 $this->assertEquals( $post->guid, $data['guid']['rendered'] ); 138 139 if ( 'edit' === $context ) { 140 $this->assertEquals( $post->guid, $data['guid']['raw'] ); 141 $this->assertEquals( $post->post_status, $data['status'] ); 142 143 if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) { 144 $this->assertNull( $data['date_gmt'] ); 145 } else { 146 $this->assertEquals( mysql_to_rfc3339( $post->post_date_gmt ), $data['date_gmt'] ); 147 } 148 149 if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) { 150 $this->assertNull( $data['modified_gmt'] ); 151 } else { 152 $this->assertEquals( mysql_to_rfc3339( $post->post_modified_gmt ), $data['modified_gmt'] ); 153 } 154 } 155 156 $taxonomies = wp_list_filter( get_object_taxonomies( $post->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 157 foreach ( $taxonomies as $taxonomy ) { 158 $this->assertTrue( isset( $data[ $taxonomy->rest_base ] ) ); 159 $terms = wp_get_object_terms( $post->ID, $taxonomy->name, array( 'fields' => 'ids' ) ); 160 sort( $terms ); 161 sort( $data[ $taxonomy->rest_base ] ); 162 $this->assertEquals( $terms, $data[ $taxonomy->rest_base ] ); 163 } 164 165 // test links 166 if ( $links ) { 167 168 $links = test_rest_expand_compact_links( $links ); 169 $post_type = get_post_type_object( $data['type'] ); 170 $this->assertEquals( $links['self'][0]['href'], rest_url( 'wp/v2/' . $post_type->rest_base . '/' . $data['id'] ) ); 171 $this->assertEquals( $links['collection'][0]['href'], rest_url( 'wp/v2/' . $post_type->rest_base ) ); 172 $this->assertEquals( $links['about'][0]['href'], rest_url( 'wp/v2/types/' . $data['type'] ) ); 173 174 if ( post_type_supports( $post->post_type, 'author' ) && $data['author'] ) { 175 $this->assertEquals( $links['author'][0]['href'], rest_url( 'wp/v2/users/' . $data['author'] ) ); 176 } 177 178 if ( post_type_supports( $post->post_type, 'comments' ) ) { 179 $this->assertEquals( $links['replies'][0]['href'], add_query_arg( 'post', $data['id'], rest_url( 'wp/v2/comments' ) ) ); 180 } 181 182 if ( post_type_supports( $post->post_type, 'revisions' ) ) { 183 $this->assertEquals( $links['version-history'][0]['href'], rest_url( 'wp/v2/' . $post_type->rest_base . '/' . $data['id'] . '/revisions' ) ); 184 } 185 186 if ( $post_type->hierarchical && ! empty( $data['parent'] ) ) { 187 $this->assertEquals( $links['up'][0]['href'], rest_url( 'wp/v2/' . $post_type->rest_base . '/' . $data['parent'] ) ); 188 } 189 190 if ( ! in_array( $data['type'], array( 'attachment', 'nav_menu_item', 'revision' ), true ) ) { 191 $this->assertEquals( $links['https://api.w.org/attachment'][0]['href'], add_query_arg( 'parent', $data['id'], rest_url( 'wp/v2/media' ) ) ); 192 } 193 194 if ( ! empty( $data['featured_media'] ) ) { 195 $this->assertEquals( $links['https://api.w.org/featuredmedia'][0]['href'], rest_url( 'wp/v2/media/' . $data['featured_media'] ) ); 196 } 197 198 $num = 0; 199 foreach ( $taxonomies as $key => $taxonomy ) { 200 $this->assertEquals( $taxonomy->name, $links['https://api.w.org/term'][ $num ]['attributes']['taxonomy'] ); 201 $this->assertEquals( add_query_arg( 'post', $data['id'], rest_url( 'wp/v2/' . $taxonomy->rest_base ) ), $links['https://api.w.org/term'][ $num ]['href'] ); 202 $num++; 203 } 204 } 205 206 } 207 208 protected function check_get_posts_response( $response, $context = 'view' ) { 209 $this->assertNotInstanceOf( 'WP_Error', $response ); 210 $response = rest_ensure_response( $response ); 211 $this->assertEquals( 200, $response->get_status() ); 212 213 $headers = $response->get_headers(); 214 $this->assertArrayHasKey( 'X-WP-Total', $headers ); 215 $this->assertArrayHasKey( 'X-WP-TotalPages', $headers ); 216 217 $all_data = $response->get_data(); 218 foreach ( $all_data as $data ) { 219 $post = get_post( $data['id'] ); 220 // as the links for the post are "response_links" format in the data array we have to pull them 221 // out and parse them. 222 $links = $data['_links']; 223 foreach ( $links as &$links_array ) { 224 foreach ( $links_array as &$link ) { 225 $attributes = array_diff_key( $link, array( 'href' => 1, 'name' => 1 ) ); 226 $link = array_diff_key( $link, $attributes ); 227 $link['attributes'] = $attributes; 228 } 229 } 230 231 $this->check_post_data( $post, $data, $context, $links ); 232 } 233 } 234 235 protected function check_get_post_response( $response, $context = 'view' ) { 236 $this->assertNotInstanceOf( 'WP_Error', $response ); 237 $response = rest_ensure_response( $response ); 238 $this->assertEquals( 200, $response->get_status() ); 239 240 $data = $response->get_data(); 241 $post = get_post( $data['id'] ); 242 $this->check_post_data( $post, $data, $context, $response->get_links() ); 243 244 } 245 246 protected function check_create_post_response( $response ) { 247 $this->assertNotInstanceOf( 'WP_Error', $response ); 248 $response = rest_ensure_response( $response ); 249 250 $this->assertEquals( 201, $response->get_status() ); 251 $headers = $response->get_headers(); 252 $this->assertArrayHasKey( 'Location', $headers ); 253 254 $data = $response->get_data(); 255 $post = get_post( $data['id'] ); 256 $this->check_post_data( $post, $data, 'edit', $response->get_links() ); 257 } 258 259 protected function check_update_post_response( $response ) { 260 $this->assertNotInstanceOf( 'WP_Error', $response ); 261 $response = rest_ensure_response( $response ); 262 263 $this->assertEquals( 200, $response->get_status() ); 264 $headers = $response->get_headers(); 265 $this->assertArrayNotHasKey( 'Location', $headers ); 266 267 $data = $response->get_data(); 268 $post = get_post( $data['id'] ); 269 $this->check_post_data( $post, $data, 'edit', $response->get_links() ); 270 } 271 272 protected function set_post_data( $args = array() ) { 273 $defaults = array( 274 'title' => rand_str(), 275 'content' => rand_str(), 276 'excerpt' => rand_str(), 277 'name' => 'test', 278 'status' => 'publish', 279 'author' => get_current_user_id(), 280 'type' => 'post', 281 ); 282 283 return wp_parse_args( $args, $defaults ); 284 } 285 286 protected function set_raw_post_data( $args = array() ) { 287 return wp_parse_args( $args, $this->set_post_data( array( 288 'title' => array( 289 'raw' => rand_str(), 290 ), 291 'content' => array( 292 'raw' => rand_str(), 293 ), 294 'excerpt' => array( 295 'raw' => rand_str(), 296 ), 297 ) ) ); 298 } 299 300 /** 301 * Overwrite the default protected title format. 302 * 303 * By default WordPress will show password protected posts with a title of 304 * "Protected: %s", as the REST API communicates the protected status of a post 305 * in a machine readable format, we remove the "Protected: " prefix. 306 * 307 * @return string 308 */ 309 public function protected_title_format() { 310 return '%s'; 311 } 312 } -
tests/phpunit/tests/rest-api/data/canola.jpg
Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream
-
tests/phpunit/tests/rest-api/data/codeispoetry.png
Property changes on: tests/phpunit/tests/rest-api/data/canola.jpg ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property
1 2 3 4 5 <!DOCTYPE html> 6 <html lang="en" class=""> 7 <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#"> 8 <meta charset='utf-8'> 9 10 <link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github-c1d91683d54d54239f36043630b76fe2b39dbc4fc91cfbd38ef9f3743e56c851.css" media="all" rel="stylesheet" /> 11 <link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github2-6b792c972e1d61f45ce186e5e65d3410c90cf78d94776abff9512f5417eb2bac.css" media="all" rel="stylesheet" /> 12 13 14 15 16 <link as="script" href="https://assets-cdn.github.com/assets/frameworks-ee521b8e9facac68ff27e93fc3ae0f8ed811d7bf9e434e84f4b9ea227780b084.js" rel="preload" /> 17 <link as="script" href="https://assets-cdn.github.com/assets/github-863d0e4c2905010278cfd87e9c7c738e812530db73c36c274a185354977a2e41.js" rel="preload" /> 18 19 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 20 <meta http-equiv="Content-Language" content="en"> 21 <meta name="viewport" content="width=1020"> 22 23 24 <title>wp-cli.github.com/codeispoetry.png at master · wp-cli/wp-cli.github.com · GitHub</title> 25 <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub"> 26 <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub"> 27 <link rel="apple-touch-icon" href="/apple-touch-icon.png"> 28 <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png"> 29 <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png"> 30 <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png"> 31 <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png"> 32 <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png"> 33 <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png"> 34 <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png"> 35 <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png"> 36 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png"> 37 <meta property="fb:app_id" content="1401488693436528"> 38 39 <meta content="https://avatars1.githubusercontent.com/u/1570774?v=3&s=400" name="twitter:image:src" /><meta content="@github" name="twitter:site" /><meta content="summary" name="twitter:card" /><meta content="wp-cli/wp-cli.github.com" name="twitter:title" /><meta content="wp-cli.github.com - wp-cli.org website" name="twitter:description" /> 40 <meta content="https://avatars1.githubusercontent.com/u/1570774?v=3&s=400" property="og:image" /><meta content="GitHub" property="og:site_name" /><meta content="object" property="og:type" /><meta content="wp-cli/wp-cli.github.com" property="og:title" /><meta content="https://github.com/wp-cli/wp-cli.github.com" property="og:url" /><meta content="wp-cli.github.com - wp-cli.org website" property="og:description" /> 41 <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats"> 42 <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors"> 43 <link rel="assets" href="https://assets-cdn.github.com/"> 44 45 <meta name="pjax-timeout" content="1000"> 46 47 48 <meta name="msapplication-TileImage" content="/windows-tile.png"> 49 <meta name="msapplication-TileColor" content="#ffffff"> 50 <meta name="selected-link" value="repo_source" data-pjax-transient> 51 52 <meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU"> 53 <meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA"> 54 <meta name="google-analytics" content="UA-3769691-2"> 55 56 <meta content="collector.githubapp.com" name="octolytics-host" /><meta content="github" name="octolytics-app-id" /><meta content="32359CA2:3F6A:789832:56BA6B37" name="octolytics-dimension-request_id" /> 57 <meta content="/<user-name>/<repo-name>/blob/show" data-pjax-transient="true" name="analytics-location" /> 58 59 60 61 <meta class="js-ga-set" name="dimension1" content="Logged Out"> 62 63 64 65 <meta name="hostname" content="github.com"> 66 <meta name="user-login" content=""> 67 68 <meta name="expected-hostname" content="github.com"> 69 <meta name="js-proxy-site-detection-payload" content="YmM2NmFhNmJmZjU3OTZhZjZiMWI2MzA3N2E0M2Y2Zjc1OTM5ODcxYjQ1OWEyYzFkMjg3Y2NkOTI1ODhiMzZmNXx7InJlbW90ZV9hZGRyZXNzIjoiNTAuNTMuMTU2LjE2MiIsInJlcXVlc3RfaWQiOiIzMjM1OUNBMjozRjZBOjc4OTgzMjo1NkJBNkIzNyJ9"> 70 71 <link rel="mask-icon" href="https://assets-cdn.github.com/pinned-octocat.svg" color="#4078c0"> 72 <link rel="icon" type="image/x-icon" href="https://assets-cdn.github.com/favicon.ico"> 73 74 <meta content="9ca3979ba13c29867416f58457ff40e521a92049" name="form-nonce" /> 75 76 <meta http-equiv="x-pjax-version" content="88e74eaed8727c649ee4773743f8723a"> 77 78 79 <meta name="description" content="wp-cli.github.com - wp-cli.org website"> 80 <meta name="go-import" content="github.com/wp-cli/wp-cli.github.com git https://github.com/wp-cli/wp-cli.github.com.git"> 81 82 <meta content="1570774" name="octolytics-dimension-user_id" /><meta content="wp-cli" name="octolytics-dimension-user_login" /><meta content="6535856" name="octolytics-dimension-repository_id" /><meta content="wp-cli/wp-cli.github.com" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="false" name="octolytics-dimension-repository_is_fork" /><meta content="6535856" name="octolytics-dimension-repository_network_root_id" /><meta content="wp-cli/wp-cli.github.com" name="octolytics-dimension-repository_network_root_nwo" /> 83 <link href="https://github.com/wp-cli/wp-cli.github.com/commits/master.atom" rel="alternate" title="Recent Commits to wp-cli.github.com:master" type="application/atom+xml"> 84 85 86 <link rel="canonical" href="https://github.com/wp-cli/wp-cli.github.com/blob/master/behat-data/codeispoetry.png" data-pjax-transient> 87 </head> 88 89 90 <body class="logged_out env-production vis-public page-blob"> 91 <a href="#start-of-content" tabindex="1" class="accessibility-aid js-skip-to-content">Skip to content</a> 92 93 94 95 96 97 98 99 100 <div class="header header-logged-out" role="banner"> 101 <div class="container clearfix"> 102 103 <a class="header-logo-wordmark" href="https://github.com/" data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark"> 104 <svg aria-hidden="true" class="octicon octicon-logo-github" height="28" role="img" version="1.1" viewBox="0 0 45 16" width="78"><path d="M8.64 5.19H4.88c-0.11 0-0.19 0.08-0.19 0.17v1.84c0 0.09 0.08 0.17 0.19 0.17h1.47v2.3s-0.33 0.11-1.25 0.11c-1.08 0-2.58-0.39-2.58-3.7s1.58-3.73 3.05-3.73c1.27 0 1.81 0.22 2.17 0.33 0.11 0.03 0.2-0.08 0.2-0.17l0.42-1.78c0-0.05-0.02-0.09-0.06-0.14-0.14-0.09-1.02-0.58-3.2-0.58C2.58 0 0 1.06 0 6.2s2.95 5.92 5.44 5.92c2.06 0 3.31-0.89 3.31-0.89 0.05-0.02 0.06-0.09 0.06-0.13V5.36c0-0.09-0.08-0.17-0.19-0.17h0.02zM27.7 0.44h-2.13c-0.09 0-0.17 0.08-0.17 0.17v4.09h-3.31V0.61c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0.17 0.08-0.17 0.17v11.11c0 0.09 0.09 0.17 0.17 0.17h2.13c0.09 0 0.17-0.08 0.17-0.17V6.97h3.31l-0.02 4.75c0 0.09 0.08 0.17 0.17 0.17h2.13c0.09 0 0.17-0.08 0.17-0.17V0.61c0-0.09-0.08-0.17-0.17-0.17h0.02zM11.19 0.69c-0.77 0-1.38 0.61-1.38 1.38s0.61 1.38 1.38 1.38c0.75 0 1.36-0.61 1.36-1.38s-0.61-1.38-1.36-1.38z m1.22 3.55c0-0.09-0.08-0.17-0.17-0.17H10.11c-0.09 0-0.17 0.09-0.17 0.2 0 0 0 6.17 0 7.34 0 0.2 0.13 0.27 0.3 0.27 0 0 0.91 0 1.92 0 0.2 0 0.25-0.09 0.25-0.27 0-0.39 0-7.36 0-7.36v-0.02z m23.52-0.16h-2.09c-0.11 0-0.17 0.08-0.17 0.19v5.44s-0.55 0.39-1.3 0.39-0.97-0.34-0.97-1.09c0-0.73 0-4.75 0-4.75 0-0.09-0.08-0.17-0.17-0.17h-2.14c-0.09 0-0.17 0.08-0.17 0.17 0 0 0 2.91 0 5.11s1.23 2.75 2.92 2.75c1.39 0 2.52-0.77 2.52-0.77s0.05 0.39 0.08 0.45c0.02 0.05 0.09 0.09 0.16 0.09h1.34c0.11 0 0.17-0.08 0.17-0.17l0.02-7.47c0-0.09-0.08-0.17-0.19-0.17z m5.77-0.25c-1.2 0-2.02 0.53-2.02 0.53V0.59c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0.17 0.08-0.17 0.17l-0.02 11.11c0 0.09 0.09 0.17 0.19 0.17h1.48c0.06 0 0.11-0.02 0.14-0.08 0.05-0.06 0.09-0.52 0.09-0.52s0.88 0.83 2.52 0.83c1.94 0 3.05-0.98 3.05-4.41s-1.77-3.88-2.97-3.88z m-0.83 6.27c-0.73-0.02-1.22-0.36-1.22-0.36V6.22s0.48-0.3 1.08-0.34c0.77-0.08 1.5 0.16 1.5 1.97 0 1.91-0.33 2.28-1.36 2.25z m-22.33-0.05c-0.09 0-0.33 0.05-0.58 0.05-0.78 0-1.05-0.36-1.05-0.83s0-3.13 0-3.13h1.59c0.09 0 0.16-0.08 0.16-0.19V4.25c0-0.09-0.08-0.17-0.16-0.17h-1.59V1.97c0-0.08-0.05-0.13-0.14-0.13H14.61c-0.09 0-0.14 0.05-0.14 0.13v2.17s-1.09 0.27-1.16 0.28c-0.08 0.02-0.13 0.09-0.13 0.17v1.36c0 0.11 0.08 0.19 0.17 0.19h1.11s0 1.44 0 3.28c0 2.44 1.7 2.69 2.86 2.69 0.53 0 1.17-0.17 1.27-0.22 0.06-0.02 0.09-0.09 0.09-0.16v-1.5c0-0.11-0.08-0.19-0.17-0.19h0.02z"></path></svg> 105 </a> 106 107 <div class="header-actions" role="navigation"> 108 <a class="btn btn-primary" href="/join?source=header-repo" data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">Sign up</a> 109 <a class="btn" href="/login?return_to=%2Fwp-cli%2Fwp-cli.github.com%2Fblob%2Fmaster%2Fbehat-data%2Fcodeispoetry.png" data-ga-click="(Logged out) Header, clicked Sign in, text:sign-in">Sign in</a> 110 </div> 111 112 <div class="site-search repo-scope js-site-search" role="search"> 113 <!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="/wp-cli/wp-cli.github.com/search" class="js-site-search-form" data-global-search-url="/search" data-repo-search-url="/wp-cli/wp-cli.github.com/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /></div> 114 <label class="js-chromeless-input-container form-control"> 115 <div class="scope-badge">This repository</div> 116 <input type="text" 117 class="js-site-search-focus js-site-search-field is-clearable chromeless-input" 118 data-hotkey="s" 119 name="q" 120 placeholder="Search" 121 aria-label="Search this repository" 122 data-global-scope-placeholder="Search GitHub" 123 data-repo-scope-placeholder="Search" 124 tabindex="1" 125 autocapitalize="off"> 126 </label> 127 </form> 128 </div> 129 130 <ul class="header-nav left" role="navigation"> 131 <li class="header-nav-item"> 132 <a class="header-nav-link" href="/explore" data-ga-click="(Logged out) Header, go to explore, text:explore">Explore</a> 133 </li> 134 <li class="header-nav-item"> 135 <a class="header-nav-link" href="/features" data-ga-click="(Logged out) Header, go to features, text:features">Features</a> 136 </li> 137 <li class="header-nav-item"> 138 <a class="header-nav-link" href="https://enterprise.github.com/" data-ga-click="(Logged out) Header, go to enterprise, text:enterprise">Enterprise</a> 139 </li> 140 <li class="header-nav-item"> 141 <a class="header-nav-link" href="/pricing" data-ga-click="(Logged out) Header, go to pricing, text:pricing">Pricing</a> 142 </li> 143 </ul> 144 145 </div> 146 </div> 147 148 149 150 <div id="start-of-content" class="accessibility-aid"></div> 151 152 <div id="js-flash-container"> 153 </div> 154 155 156 <div role="main" class="main-content"> 157 <div itemscope itemtype="http://schema.org/WebPage"> 158 <div id="js-repo-pjax-container" class="context-loader-container js-repo-nav-next" data-pjax-container> 159 160 <div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav"> 161 <div class="container repohead-details-container"> 162 163 164 165 <ul class="pagehead-actions"> 166 167 <li> 168 <a href="/login?return_to=%2Fwp-cli%2Fwp-cli.github.com" 169 class="btn btn-sm btn-with-count tooltipped tooltipped-n" 170 aria-label="You must be signed in to watch a repository" rel="nofollow"> 171 <svg aria-hidden="true" class="octicon octicon-eye" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6c4.94 0 7.94-6 7.94-6S13 2 8.06 2z m-0.06 10c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4z m2-4c0 1.11-0.89 2-2 2s-2-0.89-2-2 0.89-2 2-2 2 0.89 2 2z"></path></svg> 172 Watch 173 </a> 174 <a class="social-count" href="/wp-cli/wp-cli.github.com/watchers"> 175 13 176 </a> 177 178 </li> 179 180 <li> 181 <a href="/login?return_to=%2Fwp-cli%2Fwp-cli.github.com" 182 class="btn btn-sm btn-with-count tooltipped tooltipped-n" 183 aria-label="You must be signed in to star a repository" rel="nofollow"> 184 <svg aria-hidden="true" class="octicon octicon-star" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M14 6l-4.9-0.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14l4.33-2.33 4.33 2.33L10.4 9.26 14 6z"></path></svg> 185 Star 186 </a> 187 188 <a class="social-count js-social-count" href="/wp-cli/wp-cli.github.com/stargazers"> 189 6 190 </a> 191 192 </li> 193 194 <li> 195 <a href="/login?return_to=%2Fwp-cli%2Fwp-cli.github.com" 196 class="btn btn-sm btn-with-count tooltipped tooltipped-n" 197 aria-label="You must be signed in to fork a repository" rel="nofollow"> 198 <svg aria-hidden="true" class="octicon octicon-repo-forked" height="16" role="img" version="1.1" viewBox="0 0 10 16" width="10"><path d="M8 1c-1.11 0-2 0.89-2 2 0 0.73 0.41 1.38 1 1.72v1.28L5 8 3 6v-1.28c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v1.78l3 3v1.78c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V9.5l3-3V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2zM2 4.2c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3 10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3-10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z"></path></svg> 199 Fork 200 </a> 201 202 <a href="/wp-cli/wp-cli.github.com/network" class="social-count"> 203 27 204 </a> 205 </li> 206 </ul> 207 208 <h1 itemscope itemtype="http://data-vocabulary.org/Breadcrumb" class="entry-title public "> 209 <svg aria-hidden="true" class="octicon octicon-repo" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M4 9h-1v-1h1v1z m0-3h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z m8-1v12c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1z m-1 10H1v2h2v-1h3v1h5V11z m0-10H2v9h9V1z"></path></svg> 210 <span class="author"><a href="/wp-cli" class="url fn" itemprop="url" rel="author"><span itemprop="title">wp-cli</span></a></span><!-- 211 --><span class="path-divider">/</span><!-- 212 --><strong><a href="/wp-cli/wp-cli.github.com" data-pjax="#js-repo-pjax-container">wp-cli.github.com</a></strong> 213 214 <span class="page-context-loader"> 215 <img alt="" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" /> 216 </span> 217 218 </h1> 219 220 </div> 221 <div class="container"> 222 223 <nav class="reponav js-repo-nav js-sidenav-container-pjax js-octicon-loaders" 224 role="navigation" 225 data-pjax="#js-repo-pjax-container"> 226 227 <a href="/wp-cli/wp-cli.github.com" aria-label="Code" aria-selected="true" class="js-selected-navigation-item selected reponav-item" data-hotkey="g c" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /wp-cli/wp-cli.github.com"> 228 <svg aria-hidden="true" class="octicon octicon-code" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M9.5 3l-1.5 1.5 3.5 3.5L8 11.5l1.5 1.5 4.5-5L9.5 3zM4.5 3L0 8l4.5 5 1.5-1.5L2.5 8l3.5-3.5L4.5 3z"></path></svg> 229 Code 230 </a> 231 232 <a href="/wp-cli/wp-cli.github.com/pulls" class="js-selected-navigation-item reponav-item" data-hotkey="g p" data-selected-links="repo_pulls /wp-cli/wp-cli.github.com/pulls"> 233 <svg aria-hidden="true" class="octicon octicon-git-pull-request" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M11 11.28c0-1.73 0-6.28 0-6.28-0.03-0.78-0.34-1.47-0.94-2.06s-1.28-0.91-2.06-0.94c0 0-1.02 0-1 0V0L4 3l3 3V4h1c0.27 0.02 0.48 0.11 0.69 0.31s0.3 0.42 0.31 0.69v6.28c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72z m-1 2.92c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2zM4 3c0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72 0 1.55 0 5.56 0 6.56-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V4.72c0.59-0.34 1-0.98 1-1.72z m-0.8 10c0 0.66-0.55 1.2-1.2 1.2s-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2z m-1.2-8.8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z"></path></svg> 234 Pull requests 235 <span class="counter">3</span> 236 </a> 237 238 <a href="/wp-cli/wp-cli.github.com/pulse" class="js-selected-navigation-item reponav-item" data-selected-links="pulse /wp-cli/wp-cli.github.com/pulse"> 239 <svg aria-hidden="true" class="octicon octicon-pulse" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0V10h3.6L4.5 8.2l0.9 5.4L9 8.5l1.6 1.5H14V8H11.5z"></path></svg> 240 Pulse 241 </a> 242 <a href="/wp-cli/wp-cli.github.com/graphs" class="js-selected-navigation-item reponav-item" data-selected-links="repo_graphs repo_contributors /wp-cli/wp-cli.github.com/graphs"> 243 <svg aria-hidden="true" class="octicon octicon-graph" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M16 14v1H0V0h1v14h15z m-11-1H3V8h2v5z m4 0H7V3h2v10z m4 0H11V6h2v7z"></path></svg> 244 Graphs 245 </a> 246 247 </nav> 248 249 </div> 250 </div> 251 252 <div class="container new-discussion-timeline experiment-repo-nav"> 253 <div class="repository-content"> 254 255 256 257 <a href="/wp-cli/wp-cli.github.com/blob/663ab6d46c67329aac052ee829cfced92c98a597/behat-data/codeispoetry.png" class="hidden js-permalink-shortcut" data-hotkey="y">Permalink</a> 258 259 <!-- blob contrib key: blob_contributors:v21:03324a4d6ead46e4f13ad85cbdf84ead --> 260 261 <div class="file-navigation js-zeroclipboard-container"> 262 263 <div class="select-menu js-menu-container js-select-menu left"> 264 <button class="btn btn-sm select-menu-button js-menu-target css-truncate" data-hotkey="w" 265 title="master" 266 type="button" aria-label="Switch branches or tags" tabindex="0" aria-haspopup="true"> 267 <i>Branch:</i> 268 <span class="js-select-button css-truncate-target">master</span> 269 </button> 270 271 <div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax aria-hidden="true"> 272 273 <div class="select-menu-modal"> 274 <div class="select-menu-header"> 275 <svg aria-label="Close" class="octicon octicon-x js-menu-close" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg> 276 <span class="select-menu-title">Switch branches/tags</span> 277 </div> 278 279 <div class="select-menu-filters"> 280 <div class="select-menu-text-filter"> 281 <input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="js-filterable-field js-navigation-enable" placeholder="Filter branches/tags"> 282 </div> 283 <div class="select-menu-tabs"> 284 <ul> 285 <li class="select-menu-tab"> 286 <a href="#" data-tab-filter="branches" data-filter-placeholder="Filter branches/tags" class="js-select-menu-tab" role="tab">Branches</a> 287 </li> 288 <li class="select-menu-tab"> 289 <a href="#" data-tab-filter="tags" data-filter-placeholder="Find a tag…" class="js-select-menu-tab" role="tab">Tags</a> 290 </li> 291 </ul> 292 </div> 293 </div> 294 295 <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches" role="menu"> 296 297 <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring"> 298 299 300 <a class="select-menu-item js-navigation-item js-navigation-open " 301 href="/wp-cli/wp-cli.github.com/blob/add-global-search/behat-data/codeispoetry.png" 302 data-name="add-global-search" 303 data-skip-pjax="true" 304 rel="nofollow"> 305 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 306 <span class="select-menu-item-text css-truncate-target" title="add-global-search"> 307 add-global-search 308 </span> 309 </a> 310 <a class="select-menu-item js-navigation-item js-navigation-open " 311 href="/wp-cli/wp-cli.github.com/blob/deb-package/behat-data/codeispoetry.png" 312 data-name="deb-package" 313 data-skip-pjax="true" 314 rel="nofollow"> 315 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 316 <span class="select-menu-item-text css-truncate-target" title="deb-package"> 317 deb-package 318 </span> 319 </a> 320 <a class="select-menu-item js-navigation-item js-navigation-open " 321 href="/wp-cli/wp-cli.github.com/blob/feature/rework-globals-section/behat-data/codeispoetry.png" 322 data-name="feature/rework-globals-section" 323 data-skip-pjax="true" 324 rel="nofollow"> 325 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 326 <span class="select-menu-item-text css-truncate-target" title="feature/rework-globals-section"> 327 feature/rework-globals-section 328 </span> 329 </a> 330 <a class="select-menu-item js-navigation-item js-navigation-open " 331 href="/wp-cli/wp-cli.github.com/blob/fix-html-command-flag-minus/behat-data/codeispoetry.png" 332 data-name="fix-html-command-flag-minus" 333 data-skip-pjax="true" 334 rel="nofollow"> 335 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 336 <span class="select-menu-item-text css-truncate-target" title="fix-html-command-flag-minus"> 337 fix-html-command-flag-minus 338 </span> 339 </a> 340 <a class="select-menu-item js-navigation-item js-navigation-open " 341 href="/wp-cli/wp-cli.github.com/blob/highlighting/behat-data/codeispoetry.png" 342 data-name="highlighting" 343 data-skip-pjax="true" 344 rel="nofollow"> 345 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 346 <span class="select-menu-item-text css-truncate-target" title="highlighting"> 347 highlighting 348 </span> 349 </a> 350 <a class="select-menu-item js-navigation-item js-navigation-open selected" 351 href="/wp-cli/wp-cli.github.com/blob/master/behat-data/codeispoetry.png" 352 data-name="master" 353 data-skip-pjax="true" 354 rel="nofollow"> 355 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 356 <span class="select-menu-item-text css-truncate-target" title="master"> 357 master 358 </span> 359 </a> 360 <a class="select-menu-item js-navigation-item js-navigation-open " 361 href="/wp-cli/wp-cli.github.com/blob/phpdoc/behat-data/codeispoetry.png" 362 data-name="phpdoc" 363 data-skip-pjax="true" 364 rel="nofollow"> 365 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 366 <span class="select-menu-item-text css-truncate-target" title="phpdoc"> 367 phpdoc 368 </span> 369 </a> 370 <a class="select-menu-item js-navigation-item js-navigation-open " 371 href="/wp-cli/wp-cli.github.com/blob/post-changes-hightlighting/behat-data/codeispoetry.png" 372 data-name="post-changes-hightlighting" 373 data-skip-pjax="true" 374 rel="nofollow"> 375 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 376 <span class="select-menu-item-text css-truncate-target" title="post-changes-hightlighting"> 377 post-changes-hightlighting 378 </span> 379 </a> 380 <a class="select-menu-item js-navigation-item js-navigation-open " 381 href="/wp-cli/wp-cli.github.com/blob/post-contributing/behat-data/codeispoetry.png" 382 data-name="post-contributing" 383 data-skip-pjax="true" 384 rel="nofollow"> 385 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 386 <span class="select-menu-item-text css-truncate-target" title="post-contributing"> 387 post-contributing 388 </span> 389 </a> 390 <a class="select-menu-item js-navigation-item js-navigation-open " 391 href="/wp-cli/wp-cli.github.com/blob/redesign-2014/behat-data/codeispoetry.png" 392 data-name="redesign-2014" 393 data-skip-pjax="true" 394 rel="nofollow"> 395 <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg> 396 <span class="select-menu-item-text css-truncate-target" title="redesign-2014"> 397 redesign-2014 398 </span> 399 </a> 400 </div> 401 402 <div class="select-menu-no-results">Nothing to show</div> 403 </div> 404 405 <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags"> 406 <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring"> 407 408 409 </div> 410 411 <div class="select-menu-no-results">Nothing to show</div> 412 </div> 413 414 </div> 415 </div> 416 </div> 417 418 <div class="btn-group right"> 419 <a href="/wp-cli/wp-cli.github.com/find/master" 420 class="js-show-file-finder btn btn-sm" 421 data-pjax 422 data-hotkey="t"> 423 Find file 424 </a> 425 <button aria-label="Copy file path to clipboard" class="js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s" data-copied-hint="Copied!" type="button">Copy path</button> 426 </div> 427 <div class="breadcrumb js-zeroclipboard-target"> 428 <span class="repo-root js-repo-root"><span itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb"><a href="/wp-cli/wp-cli.github.com" class="" data-branch="master" data-pjax="true" itemscope="url"><span itemprop="title">wp-cli.github.com</span></a></span></span><span class="separator">/</span><span itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb"><a href="/wp-cli/wp-cli.github.com/tree/master/behat-data" class="" data-branch="master" data-pjax="true" itemscope="url"><span itemprop="title">behat-data</span></a></span><span class="separator">/</span><strong class="final-path">codeispoetry.png</strong> 429 </div> 430 </div> 431 432 433 <div class="commit-tease"> 434 <span class="right"> 435 <a class="commit-tease-sha" href="/wp-cli/wp-cli.github.com/commit/9a03d83f0b0cfe167a8927cf199f4113cb5bd7f3" data-pjax> 436 9a03d83 437 </a> 438 <time datetime="2016-02-03T13:13:36Z" is="relative-time">Feb 3, 2016</time> 439 </span> 440 <div> 441 <img alt="@danielbachhuber" class="avatar" height="20" src="https://avatars3.githubusercontent.com/u/36432?v=3&s=40" width="20" /> 442 <a href="/danielbachhuber" class="user-mention" rel="contributor">danielbachhuber</a> 443 <a href="/wp-cli/wp-cli.github.com/commit/9a03d83f0b0cfe167a8927cf199f4113cb5bd7f3" class="message" data-pjax="true" title="Add IPTC data to the test image">Add IPTC data to the test image</a> 444 </div> 445 446 <div class="commit-tease-contributors"> 447 <a class="muted-link contributors-toggle" href="#blob_contributors_box" rel="facebox"> 448 <strong>1</strong> 449 contributor 450 </a> 451 452 </div> 453 454 <div id="blob_contributors_box" style="display:none"> 455 <h2 class="facebox-header" data-facebox-id="facebox-header">Users who have contributed to this file</h2> 456 <ul class="facebox-user-list" data-facebox-id="facebox-description"> 457 <li class="facebox-user-list-item"> 458 <img alt="@danielbachhuber" height="24" src="https://avatars1.githubusercontent.com/u/36432?v=3&s=48" width="24" /> 459 <a href="/danielbachhuber">danielbachhuber</a> 460 </li> 461 </ul> 462 </div> 463 </div> 464 465 <div class="file"> 466 <div class="file-header"> 467 <div class="file-actions"> 468 469 <div class="btn-group"> 470 <a href="/wp-cli/wp-cli.github.com/raw/master/behat-data/codeispoetry.png" class="btn btn-sm " id="raw-url">Raw</a> 471 <a href="/wp-cli/wp-cli.github.com/commits/master/behat-data/codeispoetry.png" class="btn btn-sm " rel="nofollow">History</a> 472 </div> 473 474 475 <!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="/wp-cli/wp-cli.github.com/delete/master/behat-data/codeispoetry.png" class="inline-form" data-form-nonce="9ca3979ba13c29867416f58457ff40e521a92049" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /><input name="authenticity_token" type="hidden" value="S96qX2oUX+Gcc45YxV/LuTaZytjKoCSdzjnCuQKuAvtK9j0OCzZK7/lSmLCAbyF+r6NfG1UoWuu7rPzR9+ax+g==" /></div> 476 <button class="btn-octicon btn-octicon-danger tooltipped tooltipped-nw" type="submit" 477 aria-label="You must be signed in to make or propose changes" data-disable-with> 478 <svg aria-hidden="true" class="octicon octicon-trashcan" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M10 2H8c0-0.55-0.45-1-1-1H4c-0.55 0-1 0.45-1 1H1c-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1v9c0 0.55 0.45 1 1 1h7c0.55 0 1-0.45 1-1V5c0.55 0 1-0.45 1-1v-1c0-0.55-0.45-1-1-1z m-1 12H2V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9z m1-10H1v-1h9v1z"></path></svg> 479 </button> 480 </form> </div> 481 482 <div class="file-info"> 483 15.6 KB 484 </div> 485 </div> 486 487 488 489 <div class="blob-wrapper data type-text"> 490 <div class="image"> 491 <span class="border-wrap"><img src="/wp-cli/wp-cli.github.com/blob/master/behat-data/codeispoetry.png?raw=true" alt="codeispoetry.png"></span> 492 </div> 493 </div> 494 495 </div> 496 497 <a href="#jump-to-line" rel="facebox[.linejump]" data-hotkey="l" style="display:none">Jump to Line</a> 498 <div id="jump-to-line" style="display:none"> 499 <!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="" class="js-jump-to-line-form" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /></div> 500 <input class="linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line…" aria-label="Jump to line" autofocus> 501 <button type="submit" class="btn">Go</button> 502 </form></div> 503 504 </div> 505 <div class="modal-backdrop"></div> 506 </div> 507 508 509 </div> 510 </div> 511 512 </div> 513 514 <div class="container"> 515 <div class="site-footer" role="contentinfo"> 516 <ul class="site-footer-links right"> 517 <li><a href="https://status.github.com/" data-ga-click="Footer, go to status, text:status">Status</a></li> 518 <li><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li> 519 <li><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li> 520 <li><a href="https://shop.github.com" data-ga-click="Footer, go to shop, text:shop">Shop</a></li> 521 <li><a href="https://github.com/blog" data-ga-click="Footer, go to blog, text:blog">Blog</a></li> 522 <li><a href="https://github.com/about" data-ga-click="Footer, go to about, text:about">About</a></li> 523 <li><a href="https://github.com/pricing" data-ga-click="Footer, go to pricing, text:pricing">Pricing</a></li> 524 525 </ul> 526 527 <a href="https://github.com" aria-label="Homepage"> 528 <svg aria-hidden="true" class="octicon octicon-mark-github" height="24" role="img" title="GitHub " version="1.1" viewBox="0 0 16 16" width="24"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55-0.17 0.55-0.38 0-0.19-0.01-0.82-0.01-1.49-2.01 0.37-2.53-0.49-2.69-0.94-0.09-0.23-0.48-0.94-0.82-1.13-0.28-0.15-0.68-0.52-0.01-0.53 0.63-0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07-0.52 0.28-0.87 0.51-1.07-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.59 0.82-2.15-0.08-0.2-0.36-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82 0.64-0.18 1.32-0.27 2-0.27 0.68 0 1.36 0.09 2 0.27 1.53-1.04 2.2-0.82 2.2-0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07-1.87 3.75-3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07-0.01 1.93-0.01 2.2 0 0.21 0.15 0.46 0.55 0.38C13.71 14.53 16 11.53 16 8 16 3.58 12.42 0 8 0z"></path></svg> 529 </a> 530 <ul class="site-footer-links"> 531 <li>© 2016 <span title="0.03841s from github-fe118-cp1-prd.iad.github.net">GitHub</span>, Inc.</li> 532 <li><a href="https://github.com/site/terms" data-ga-click="Footer, go to terms, text:terms">Terms</a></li> 533 <li><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a></li> 534 <li><a href="https://github.com/security" data-ga-click="Footer, go to security, text:security">Security</a></li> 535 <li><a href="https://github.com/contact" data-ga-click="Footer, go to contact, text:contact">Contact</a></li> 536 <li><a href="https://help.github.com" data-ga-click="Footer, go to help, text:help">Help</a></li> 537 </ul> 538 </div> 539 </div> 540 541 542 543 544 545 546 547 <div id="ajax-error-message" class="flash flash-error"> 548 <svg aria-hidden="true" class="octicon octicon-alert" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M15.72 12.5l-6.85-11.98C8.69 0.21 8.36 0.02 8 0.02s-0.69 0.19-0.87 0.5l-6.85 11.98c-0.18 0.31-0.18 0.69 0 1C0.47 13.81 0.8 14 1.15 14h13.7c0.36 0 0.69-0.19 0.86-0.5S15.89 12.81 15.72 12.5zM9 12H7V10h2V12zM9 9H7V5h2V9z"></path></svg> 549 <button type="button" class="flash-close js-flash-close js-ajax-error-dismiss" aria-label="Dismiss error"> 550 <svg aria-hidden="true" class="octicon octicon-x" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg> 551 </button> 552 Something went wrong with that request. Please try again. 553 </div> 554 555 556 <script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/compat-ef12e71982f00805539829712436829b02370b2e561de18ece6796130cbb9bbe.js"></script> 557 <script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/frameworks-ee521b8e9facac68ff27e93fc3ae0f8ed811d7bf9e434e84f4b9ea227780b084.js"></script> 558 <script async="async" crossorigin="anonymous" src="https://assets-cdn.github.com/assets/github-863d0e4c2905010278cfd87e9c7c738e812530db73c36c274a185354977a2e41.js"></script> 559 560 561 562 <div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner hidden"> 563 <svg aria-hidden="true" class="octicon octicon-alert" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M15.72 12.5l-6.85-11.98C8.69 0.21 8.36 0.02 8 0.02s-0.69 0.19-0.87 0.5l-6.85 11.98c-0.18 0.31-0.18 0.69 0 1C0.47 13.81 0.8 14 1.15 14h13.7c0.36 0 0.69-0.19 0.86-0.5S15.89 12.81 15.72 12.5zM9 12H7V10h2V12zM9 9H7V5h2V9z"></path></svg> 564 <span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span> 565 <span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span> 566 </div> 567 <div class="facebox" id="facebox" style="display:none;"> 568 <div class="facebox-popup"> 569 <div class="facebox-content" role="dialog" aria-labelledby="facebox-header" aria-describedby="facebox-description"> 570 </div> 571 <button type="button" class="facebox-close js-facebox-close" aria-label="Close modal"> 572 <svg aria-hidden="true" class="octicon octicon-x" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg> 573 </button> 574 </div> 575 </div> 576 577 </body> 578 </html> 579 -
tests/phpunit/tests/rest-api/rest-attachments-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Attachments_Controller functionality 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Controller_Testcase { 13 14 public function setUp() { 15 parent::setUp(); 16 17 $this->editor_id = $this->factory->user->create( array( 18 'role' => 'editor', 19 ) ); 20 $this->author_id = $this->factory->user->create( array( 21 'role' => 'author', 22 ) ); 23 $this->contributor_id = $this->factory->user->create( array( 24 'role' => 'contributor', 25 ) ); 26 27 // Add an uploader role to test upload capabilities. 28 add_role( 'uploader', 'File upload role' ); 29 $role = get_role( 'uploader' ); 30 $role->add_cap( 'upload_files' ); 31 $role->add_cap( 'read' ); 32 $role->add_cap( 'level_0' ); 33 $this->uploader_id = $this->factory->user->create( array( 34 'role' => 'uploader', 35 ) ); 36 37 $orig_file = dirname( __FILE__ ) . '/data/canola.jpg'; 38 $this->test_file = '/tmp/canola.jpg'; 39 copy( $orig_file, $this->test_file ); 40 $orig_file2 = dirname( __FILE__ ) . '/data/codeispoetry.png'; 41 $this->test_file2 = '/tmp/codeispoetry.png'; 42 copy( $orig_file2, $this->test_file2 ); 43 44 } 45 46 public function test_register_routes() { 47 $routes = $this->server->get_routes(); 48 $this->assertArrayHasKey( '/wp/v2/media', $routes ); 49 $this->assertCount( 2, $routes['/wp/v2/media'] ); 50 $this->assertArrayHasKey( '/wp/v2/media/(?P<id>[\d]+)', $routes ); 51 $this->assertCount( 3, $routes['/wp/v2/media/(?P<id>[\d]+)'] ); 52 } 53 54 public static function disposition_provider() { 55 return array( 56 // Types 57 array( 'attachment; filename="foo.jpg"', 'foo.jpg' ), 58 array( 'inline; filename="foo.jpg"', 'foo.jpg' ), 59 array( 'form-data; filename="foo.jpg"', 'foo.jpg' ), 60 61 // Formatting 62 array( 'attachment; filename="foo.jpg"', 'foo.jpg' ), 63 array( 'attachment; filename=foo.jpg', 'foo.jpg' ), 64 array( 'attachment;filename="foo.jpg"', 'foo.jpg' ), 65 array( 'attachment;filename=foo.jpg', 'foo.jpg' ), 66 array( 'attachment; filename = "foo.jpg"', 'foo.jpg' ), 67 array( 'attachment; filename = foo.jpg', 'foo.jpg' ), 68 array( "attachment;\tfilename\t=\t\"foo.jpg\"", 'foo.jpg' ), 69 array( "attachment;\tfilename\t=\tfoo.jpg", 'foo.jpg' ), 70 array( 'attachment; filename = my foo picture.jpg', 'my foo picture.jpg' ), 71 72 // Extensions 73 array( 'form-data; name="myfile"; filename="foo.jpg"', 'foo.jpg' ), 74 array( 'form-data; name="myfile"; filename="foo.jpg"; something="else"', 'foo.jpg' ), 75 array( 'form-data; name=myfile; filename=foo.jpg; something=else', 'foo.jpg' ), 76 array( 'form-data; name=myfile; filename=my foo.jpg; something=else', 'my foo.jpg' ), 77 78 // Invalid 79 array( 'filename="foo.jpg"', null ), 80 array( 'filename-foo.jpg', null ), 81 array( 'foo.jpg', null ), 82 array( 'unknown; notfilename="foo.jpg"', null ), 83 ); 84 } 85 86 /** 87 * @dataProvider disposition_provider 88 */ 89 public function test_parse_disposition( $header, $expected ) { 90 $header_list = array( $header ); 91 $parsed = WP_REST_Attachments_Controller::get_filename_from_disposition( $header_list ); 92 $this->assertEquals( $expected, $parsed ); 93 } 94 95 public function test_context_param() { 96 // Collection 97 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' ); 98 $response = $this->server->dispatch( $request ); 99 $data = $response->get_data(); 100 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 101 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 102 // Single 103 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 104 'post_mime_type' => 'image/jpeg', 105 'post_excerpt' => 'A sample caption', 106 ) ); 107 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media/' . $attachment_id ); 108 $response = $this->server->dispatch( $request ); 109 $data = $response->get_data(); 110 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 111 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 112 } 113 114 public function test_registered_query_params() { 115 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' ); 116 $response = $this->server->dispatch( $request ); 117 $data = $response->get_data(); 118 $keys = array_keys( $data['endpoints'][0]['args'] ); 119 sort( $keys ); 120 $this->assertEquals( array( 121 'after', 122 'author', 123 'author_exclude', 124 'before', 125 'context', 126 'exclude', 127 'filter', 128 'include', 129 'media_type', 130 'mime_type', 131 'offset', 132 'order', 133 'orderby', 134 'page', 135 'parent', 136 'parent_exclude', 137 'per_page', 138 'search', 139 'slug', 140 'status', 141 ), $keys ); 142 $media_types = array( 143 'application', 144 'video', 145 'image', 146 'audio', 147 ); 148 if ( ! is_multisite() ) { 149 $media_types[] = 'text'; 150 } 151 $this->assertEqualSets( $media_types, $data['endpoints'][0]['args']['media_type']['enum'] ); 152 } 153 154 public function test_get_items() { 155 wp_set_current_user( 0 ); 156 $id1 = $this->factory->attachment->create_object( $this->test_file, 0, array( 157 'post_mime_type' => 'image/jpeg', 158 'post_excerpt' => 'A sample caption', 159 ) ); 160 $draft_post = $this->factory->post->create( array( 'post_status' => 'draft' ) ); 161 $id2 = $this->factory->attachment->create_object( $this->test_file, $draft_post, array( 162 'post_mime_type' => 'image/jpeg', 163 'post_excerpt' => 'A sample caption', 164 ) ); 165 $published_post = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 166 $id3 = $this->factory->attachment->create_object( $this->test_file, $published_post, array( 167 'post_mime_type' => 'image/jpeg', 168 'post_excerpt' => 'A sample caption', 169 ) ); 170 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 171 $response = $this->server->dispatch( $request ); 172 $data = $response->get_data(); 173 $this->assertCount( 2, $data ); 174 $ids = wp_list_pluck( $data, 'id' ); 175 $this->assertTrue( in_array( $id1, $ids, true ) ); 176 $this->assertFalse( in_array( $id2, $ids, true ) ); 177 $this->assertTrue( in_array( $id3, $ids, true ) ); 178 179 $this->check_get_posts_response( $response ); 180 } 181 182 public function test_get_items_logged_in_editor() { 183 wp_set_current_user( $this->editor_id ); 184 $id1 = $this->factory->attachment->create_object( $this->test_file, 0, array( 185 'post_mime_type' => 'image/jpeg', 186 'post_excerpt' => 'A sample caption', 187 ) ); 188 $draft_post = $this->factory->post->create( array( 'post_status' => 'draft' ) ); 189 $id2 = $this->factory->attachment->create_object( $this->test_file, $draft_post, array( 190 'post_mime_type' => 'image/jpeg', 191 'post_excerpt' => 'A sample caption', 192 ) ); 193 $published_post = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 194 $id3 = $this->factory->attachment->create_object( $this->test_file, $published_post, array( 195 'post_mime_type' => 'image/jpeg', 196 'post_excerpt' => 'A sample caption', 197 ) ); 198 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 199 $response = $this->server->dispatch( $request ); 200 201 $data = $response->get_data(); 202 $this->assertCount( 3, $data ); 203 $ids = wp_list_pluck( $data, 'id' ); 204 $this->assertTrue( in_array( $id1, $ids, true ) ); 205 $this->assertTrue( in_array( $id2, $ids, true ) ); 206 $this->assertTrue( in_array( $id3, $ids, true ) ); 207 } 208 209 public function test_get_items_media_type() { 210 $id1 = $this->factory->attachment->create_object( $this->test_file, 0, array( 211 'post_mime_type' => 'image/jpeg', 212 ) ); 213 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 214 $response = $this->server->dispatch( $request ); 215 $data = $response->get_data(); 216 $this->assertEquals( $id1, $data[0]['id'] ); 217 // media_type=video 218 $request->set_param( 'media_type', 'video' ); 219 $response = $this->server->dispatch( $request ); 220 $this->assertCount( 0, $response->get_data() ); 221 // media_type=image 222 $request->set_param( 'media_type', 'image' ); 223 $response = $this->server->dispatch( $request ); 224 $data = $response->get_data(); 225 $this->assertEquals( $id1, $data[0]['id'] ); 226 } 227 228 public function test_get_items_mime_type() { 229 $id1 = $this->factory->attachment->create_object( $this->test_file, 0, array( 230 'post_mime_type' => 'image/jpeg', 231 ) ); 232 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 233 $response = $this->server->dispatch( $request ); 234 $data = $response->get_data(); 235 $this->assertEquals( $id1, $data[0]['id'] ); 236 // mime_type=image/png 237 $request->set_param( 'mime_type', 'image/png' ); 238 $response = $this->server->dispatch( $request ); 239 $this->assertCount( 0, $response->get_data() ); 240 // mime_type=image/jpeg 241 $request->set_param( 'mime_type', 'image/jpeg' ); 242 $response = $this->server->dispatch( $request ); 243 $data = $response->get_data(); 244 $this->assertEquals( $id1, $data[0]['id'] ); 245 } 246 247 public function test_get_items_parent() { 248 $post_id = $this->factory->post->create( array( 'post_title' => 'Test Post' ) ); 249 $attachment_id = $this->factory->attachment->create_object( $this->test_file, $post_id, array( 250 'post_mime_type' => 'image/jpeg', 251 'post_excerpt' => 'A sample caption', 252 ) ); 253 $attachment_id2 = $this->factory->attachment->create_object( $this->test_file, 0, array( 254 'post_mime_type' => 'image/jpeg', 255 'post_excerpt' => 'A sample caption', 256 ) ); 257 // all attachments 258 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 259 $response = $this->server->dispatch( $request ); 260 $this->assertEquals( 2, count( $response->get_data() ) ); 261 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 262 // attachments without a parent 263 $request->set_param( 'parent', 0 ); 264 $response = $this->server->dispatch( $request ); 265 $data = $response->get_data(); 266 $this->assertEquals( 1, count( $data ) ); 267 $this->assertEquals( $attachment_id2, $data[0]['id'] ); 268 // attachments with parent=post_id 269 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 270 $request->set_param( 'parent', $post_id ); 271 $response = $this->server->dispatch( $request ); 272 $data = $response->get_data(); 273 $this->assertEquals( 1, count( $data ) ); 274 $this->assertEquals( $attachment_id, $data[0]['id'] ); 275 // attachments with invalid parent 276 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 277 $request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 278 $response = $this->server->dispatch( $request ); 279 $data = $response->get_data(); 280 $this->assertEquals( 0, count( $data ) ); 281 } 282 283 public function test_get_items_invalid_status_param_is_discarded() { 284 wp_set_current_user( $this->editor_id ); 285 $this->factory->attachment->create_object( $this->test_file, 0, array( 286 'post_mime_type' => 'image/jpeg', 287 'post_excerpt' => 'A sample caption', 288 ) ); 289 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 290 $request->set_param( 'status', 'publish' ); 291 $request->set_param( 'context', 'edit' ); 292 $response = $this->server->dispatch( $request ); 293 $data = $response->get_data(); 294 $this->assertCount( 1, $data ); 295 $this->assertEquals( 'inherit', $data[0]['status'] ); 296 } 297 298 public function test_get_items_private_status() { 299 // Logged out users can't make the request 300 wp_set_current_user( 0 ); 301 $attachment_id1 = $this->factory->attachment->create_object( $this->test_file, 0, array( 302 'post_mime_type' => 'image/jpeg', 303 'post_excerpt' => 'A sample caption', 304 'post_status' => 'private', 305 ) ); 306 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 307 $request->set_param( 'status', 'private' ); 308 $response = $this->server->dispatch( $request ); 309 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 310 // Properly authorized users can make the request 311 wp_set_current_user( $this->editor_id ); 312 $response = $this->server->dispatch( $request ); 313 $this->assertEquals( 200, $response->get_status() ); 314 $data = $response->get_data(); 315 $this->assertEquals( $attachment_id1, $data[0]['id'] ); 316 } 317 318 public function test_get_items_invalid_date() { 319 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 320 $request->set_param( 'after', rand_str() ); 321 $request->set_param( 'before', rand_str() ); 322 $response = $this->server->dispatch( $request ); 323 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 324 } 325 326 public function test_get_items_valid_date() { 327 $id1 = $this->factory->attachment->create_object( $this->test_file, 0, array( 328 'post_date' => '2016-01-15T00:00:00Z', 329 'post_mime_type' => 'image/jpeg', 330 'post_excerpt' => 'A sample caption', 331 ) ); 332 $id2 = $this->factory->attachment->create_object( $this->test_file, 0, array( 333 'post_date' => '2016-01-16T00:00:00Z', 334 'post_mime_type' => 'image/jpeg', 335 'post_excerpt' => 'A sample caption', 336 ) ); 337 $id3 = $this->factory->attachment->create_object( $this->test_file, 0, array( 338 'post_date' => '2016-01-17T00:00:00Z', 339 'post_mime_type' => 'image/jpeg', 340 'post_excerpt' => 'A sample caption', 341 ) ); 342 $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); 343 $request->set_param( 'after', '2016-01-15T00:00:00Z' ); 344 $request->set_param( 'before', '2016-01-17T00:00:00Z' ); 345 $response = $this->server->dispatch( $request ); 346 $data = $response->get_data(); 347 $this->assertCount( 1, $data ); 348 $this->assertEquals( $id2, $data[0]['id'] ); 349 } 350 351 public function test_get_item() { 352 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 353 'post_mime_type' => 'image/jpeg', 354 'post_excerpt' => 'A sample caption', 355 ) ); 356 update_post_meta( $attachment_id, '_wp_attachment_image_alt', 'Sample alt text' ); 357 $request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id ); 358 $response = $this->server->dispatch( $request ); 359 $this->check_get_post_response( $response ); 360 $data = $response->get_data(); 361 $this->assertEquals( 'image/jpeg', $data['mime_type'] ); 362 } 363 364 public function test_get_item_sizes() { 365 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 366 'post_mime_type' => 'image/jpeg', 367 'post_excerpt' => 'A sample caption', 368 ), $this->test_file ); 369 370 add_image_size( 'rest-api-test', 119, 119, true ); 371 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $this->test_file ) ); 372 373 $request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id ); 374 $response = $this->server->dispatch( $request ); 375 $data = $response->get_data(); 376 $image_src = wp_get_attachment_image_src( $attachment_id, 'rest-api-test' ); 377 $original_image_src = wp_get_attachment_image_src( $attachment_id, 'full' ); 378 remove_image_size( 'rest-api-test' ); 379 380 $this->assertEquals( $image_src[0], $data['media_details']['sizes']['rest-api-test']['source_url'] ); 381 $this->assertEquals( 'image/jpeg', $data['media_details']['sizes']['rest-api-test']['mime_type'] ); 382 $this->assertEquals( $original_image_src[0], $data['media_details']['sizes']['full']['source_url'] ); 383 $this->assertEquals( 'image/jpeg', $data['media_details']['sizes']['full']['mime_type'] ); 384 } 385 386 public function test_get_item_sizes_with_no_url() { 387 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 388 'post_mime_type' => 'image/jpeg', 389 'post_excerpt' => 'A sample caption', 390 ), $this->test_file ); 391 392 add_image_size( 'rest-api-test', 119, 119, true ); 393 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $this->test_file ) ); 394 395 add_filter( 'wp_get_attachment_image_src', '__return_false' ); 396 397 $request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id ); 398 $response = $this->server->dispatch( $request ); 399 $data = $response->get_data(); 400 remove_filter( 'wp_get_attachment_image_src', '__return_false' ); 401 remove_image_size( 'rest-api-test' ); 402 403 $this->assertFalse( isset( $data['media_details']['sizes']['rest-api-test']['source_url'] ) ); 404 } 405 406 public function test_get_item_private_post() { 407 wp_set_current_user( 0 ); 408 $draft_post = $this->factory->post->create( array( 'post_status' => 'draft' ) ); 409 $id1 = $this->factory->attachment->create_object( $this->test_file, $draft_post, array( 410 'post_mime_type' => 'image/jpeg', 411 'post_excerpt' => 'A sample caption', 412 ) ); 413 $request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $id1 ); 414 $response = $this->server->dispatch( $request ); 415 $this->assertEquals( 403, $response->get_status() ); 416 } 417 418 public function test_create_item() { 419 wp_set_current_user( $this->author_id ); 420 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 421 $request->set_header( 'Content-Type', 'image/jpeg' ); 422 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' ); 423 $request->set_body( file_get_contents( $this->test_file ) ); 424 $response = $this->server->dispatch( $request ); 425 $data = $response->get_data(); 426 $this->assertEquals( 201, $response->get_status() ); 427 $this->assertEquals( 'image', $data['media_type'] ); 428 $this->assertEquals( 'A field of amazing canola', $data['title']['rendered'] ); 429 $this->assertEquals( 'The description for the image', $data['caption'] ); 430 } 431 432 public function test_create_item_default_filename_title() { 433 wp_set_current_user( $this->author_id ); 434 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 435 $request->set_file_params( array( 436 'file' => array( 437 'file' => file_get_contents( $this->test_file2 ), 438 'name' => 'codeispoetry.jpg', 439 'size' => filesize( $this->test_file2 ), 440 'tmp_name' => $this->test_file2, 441 ), 442 ) ); 443 $request->set_header( 'Content-MD5', md5_file( $this->test_file2 ) ); 444 $response = $this->server->dispatch( $request ); 445 $this->assertEquals( 201, $response->get_status() ); 446 $data = $response->get_data(); 447 $this->assertEquals( 'codeispoetry', $data['title']['raw'] ); 448 } 449 450 public function test_create_item_with_files() { 451 wp_set_current_user( $this->author_id ); 452 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 453 $request->set_file_params( array( 454 'file' => array( 455 'file' => file_get_contents( $this->test_file ), 456 'name' => 'canola.jpg', 457 'size' => filesize( $this->test_file ), 458 'tmp_name' => $this->test_file, 459 ), 460 ) ); 461 $request->set_header( 'Content-MD5', md5_file( $this->test_file ) ); 462 $response = $this->server->dispatch( $request ); 463 $this->assertEquals( 201, $response->get_status() ); 464 } 465 466 public function test_create_item_with_upload_files_role() { 467 wp_set_current_user( $this->uploader_id ); 468 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 469 $request->set_file_params( array( 470 'file' => array( 471 'file' => file_get_contents( $this->test_file ), 472 'name' => 'canola.jpg', 473 'size' => filesize( $this->test_file ), 474 'tmp_name' => $this->test_file, 475 ), 476 ) ); 477 $request->set_header( 'Content-MD5', md5_file( $this->test_file ) ); 478 $response = $this->server->dispatch( $request ); 479 $this->assertEquals( 201, $response->get_status() ); 480 } 481 482 public function test_create_item_empty_body() { 483 wp_set_current_user( $this->author_id ); 484 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 485 $response = $this->server->dispatch( $request ); 486 $this->assertErrorResponse( 'rest_upload_no_data', $response, 400 ); 487 } 488 489 public function test_create_item_missing_content_type() { 490 wp_set_current_user( $this->author_id ); 491 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 492 $request->set_body( file_get_contents( $this->test_file ) ); 493 $response = $this->server->dispatch( $request ); 494 $this->assertErrorResponse( 'rest_upload_no_content_type', $response, 400 ); 495 } 496 497 public function test_create_item_missing_content_disposition() { 498 wp_set_current_user( $this->author_id ); 499 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 500 $request->set_header( 'Content-Type', 'image/jpeg' ); 501 $request->set_body( file_get_contents( $this->test_file ) ); 502 $response = $this->server->dispatch( $request ); 503 $this->assertErrorResponse( 'rest_upload_no_content_disposition', $response, 400 ); 504 } 505 506 public function test_create_item_bad_md5_header() { 507 wp_set_current_user( $this->author_id ); 508 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 509 $request->set_header( 'Content-Type', 'image/jpeg' ); 510 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' ); 511 $request->set_header( 'Content-MD5', 'abc123' ); 512 $request->set_body( file_get_contents( $this->test_file ) ); 513 $response = $this->server->dispatch( $request ); 514 $this->assertErrorResponse( 'rest_upload_hash_mismatch', $response, 412 ); 515 } 516 517 public function test_create_item_with_files_bad_md5_header() { 518 wp_set_current_user( $this->author_id ); 519 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 520 $request->set_file_params( array( 521 'file' => array( 522 'file' => file_get_contents( $this->test_file ), 523 'name' => 'canola.jpg', 524 'size' => filesize( $this->test_file ), 525 'tmp_name' => $this->test_file, 526 ), 527 ) ); 528 $request->set_header( 'Content-MD5', 'abc123' ); 529 $response = $this->server->dispatch( $request ); 530 $this->assertErrorResponse( 'rest_upload_hash_mismatch', $response, 412 ); 531 } 532 533 public function test_create_item_invalid_upload_files_capability() { 534 wp_set_current_user( $this->contributor_id ); 535 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 536 $response = $this->server->dispatch( $request ); 537 $this->assertErrorResponse( 'rest_cannot_create', $response, 403 ); 538 } 539 540 public function test_create_item_invalid_edit_permissions() { 541 $post_id = $this->factory->post->create( array( 'post_author' => $this->editor_id ) ); 542 wp_set_current_user( $this->author_id ); 543 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 544 $request->set_param( 'post', $post_id ); 545 $response = $this->server->dispatch( $request ); 546 $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); 547 } 548 549 public function test_create_item_invalid_upload_permissions() { 550 $post_id = $this->factory->post->create( array( 'post_author' => $this->editor_id ) ); 551 wp_set_current_user( $this->uploader_id ); 552 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 553 $request->set_param( 'post', $post_id ); 554 $response = $this->server->dispatch( $request ); 555 $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); 556 } 557 558 public function test_create_item_invalid_post_type() { 559 $attachment_id = $this->factory->post->create( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_parent' => 0 ) ); 560 wp_set_current_user( $this->editor_id ); 561 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 562 $request->set_header( 'Content-Type', 'image/jpeg' ); 563 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' ); 564 $request->set_body( file_get_contents( $this->test_file ) ); 565 $request->set_param( 'post', $attachment_id ); 566 $response = $this->server->dispatch( $request ); 567 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 568 } 569 570 public function test_create_item_alt_text() { 571 wp_set_current_user( $this->author_id ); 572 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 573 $request->set_header( 'Content-Type', 'image/jpeg' ); 574 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' ); 575 576 $request->set_body( file_get_contents( $this->test_file ) ); 577 $request->set_param( 'alt_text', 'test alt text' ); 578 $response = $this->server->dispatch( $request ); 579 $attachment = $response->get_data(); 580 $this->assertEquals( 'test alt text', $attachment['alt_text'] ); 581 } 582 583 public function test_create_item_unsafe_alt_text() { 584 wp_set_current_user( $this->author_id ); 585 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 586 $request->set_header( 'Content-Type', 'image/jpeg' ); 587 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' ); 588 $request->set_body( file_get_contents( $this->test_file ) ); 589 $request->set_param( 'alt_text', '<script>alert(document.cookie)</script>' ); 590 $response = $this->server->dispatch( $request ); 591 $attachment = $response->get_data(); 592 $this->assertEquals( '', $attachment['alt_text'] ); 593 } 594 595 public function test_update_item() { 596 wp_set_current_user( $this->editor_id ); 597 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 598 'post_mime_type' => 'image/jpeg', 599 'post_excerpt' => 'A sample caption', 600 'post_author' => $this->editor_id, 601 ) ); 602 $request = new WP_REST_Request( 'POST', '/wp/v2/media/' . $attachment_id ); 603 $request->set_param( 'title', 'My title is very cool' ); 604 $request->set_param( 'caption', 'This is a better caption.' ); 605 $request->set_param( 'description', 'Without a description, my attachment is descriptionless.' ); 606 $request->set_param( 'alt_text', 'Alt text is stored outside post schema.' ); 607 $response = $this->server->dispatch( $request ); 608 $data = $response->get_data(); 609 $attachment = get_post( $data['id'] ); 610 $this->assertEquals( 'My title is very cool', $data['title']['raw'] ); 611 $this->assertEquals( 'My title is very cool', $attachment->post_title ); 612 $this->assertEquals( 'This is a better caption.', $data['caption'] ); 613 $this->assertEquals( 'This is a better caption.', $attachment->post_excerpt ); 614 $this->assertEquals( 'Without a description, my attachment is descriptionless.', $data['description'] ); 615 $this->assertEquals( 'Without a description, my attachment is descriptionless.', $attachment->post_content ); 616 $this->assertEquals( 'Alt text is stored outside post schema.', $data['alt_text'] ); 617 $this->assertEquals( 'Alt text is stored outside post schema.', get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ) ); 618 } 619 620 public function test_update_item_parent() { 621 wp_set_current_user( $this->editor_id ); 622 $original_parent = $this->factory->post->create( array() ); 623 $attachment_id = $this->factory->attachment->create_object( $this->test_file, $original_parent, array( 624 'post_mime_type' => 'image/jpeg', 625 'post_excerpt' => 'A sample caption', 626 'post_author' => $this->editor_id, 627 ) ); 628 629 $attachment = get_post( $attachment_id ); 630 $this->assertEquals( $original_parent, $attachment->post_parent ); 631 632 $new_parent = $this->factory->post->create( array() ); 633 $request = new WP_REST_Request( 'POST', '/wp/v2/media/' . $attachment_id ); 634 $request->set_param( 'post', $new_parent ); 635 $this->server->dispatch( $request ); 636 637 $attachment = get_post( $attachment_id ); 638 $this->assertEquals( $new_parent, $attachment->post_parent ); 639 } 640 641 public function test_update_item_invalid_permissions() { 642 wp_set_current_user( $this->author_id ); 643 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 644 'post_mime_type' => 'image/jpeg', 645 'post_excerpt' => 'A sample caption', 646 'post_author' => $this->editor_id, 647 ) ); 648 $request = new WP_REST_Request( 'POST', '/wp/v2/media/' . $attachment_id ); 649 $request->set_param( 'caption', 'This is a better caption.' ); 650 $response = $this->server->dispatch( $request ); 651 $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); 652 } 653 654 public function test_update_item_invalid_post_type() { 655 $attachment_id = $this->factory->post->create( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_parent' => 0 ) ); 656 wp_set_current_user( $this->editor_id ); 657 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 658 'post_mime_type' => 'image/jpeg', 659 'post_excerpt' => 'A sample caption', 660 'post_author' => $this->editor_id, 661 ) ); 662 $request = new WP_REST_Request( 'POST', '/wp/v2/media/' . $attachment_id ); 663 $request->set_param( 'post', $attachment_id ); 664 $response = $this->server->dispatch( $request ); 665 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 666 } 667 668 public function test_delete_item() { 669 wp_set_current_user( $this->editor_id ); 670 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 671 'post_mime_type' => 'image/jpeg', 672 'post_excerpt' => 'A sample caption', 673 ) ); 674 $request = new WP_REST_Request( 'DELETE', '/wp/v2/media/' . $attachment_id ); 675 $request['force'] = true; 676 $response = $this->server->dispatch( $request ); 677 $this->assertEquals( 200, $response->get_status() ); 678 } 679 680 public function test_delete_item_no_trash() { 681 wp_set_current_user( $this->editor_id ); 682 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 683 'post_mime_type' => 'image/jpeg', 684 'post_excerpt' => 'A sample caption', 685 ) ); 686 687 // Attempt trashing 688 $request = new WP_REST_Request( 'DELETE', '/wp/v2/media/' . $attachment_id ); 689 $response = $this->server->dispatch( $request ); 690 $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 ); 691 692 // Ensure the post still exists 693 $post = get_post( $attachment_id ); 694 $this->assertNotEmpty( $post ); 695 } 696 697 public function test_delete_item_invalid_delete_permissions() { 698 wp_set_current_user( $this->author_id ); 699 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 700 'post_mime_type' => 'image/jpeg', 701 'post_excerpt' => 'A sample caption', 702 'post_author' => $this->editor_id, 703 ) ); 704 $request = new WP_REST_Request( 'DELETE', '/wp/v2/media/' . $attachment_id ); 705 $response = $this->server->dispatch( $request ); 706 $this->assertErrorResponse( 'rest_cannot_delete', $response, 403 ); 707 } 708 709 public function test_prepare_item() { 710 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 711 'post_mime_type' => 'image/jpeg', 712 'post_excerpt' => 'A sample caption', 713 'post_author' => $this->editor_id, 714 ) ); 715 716 $attachment = get_post( $attachment_id ); 717 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/media/%d', $attachment_id ) ); 718 $response = $this->server->dispatch( $request ); 719 $data = $response->get_data(); 720 $this->check_post_data( $attachment, $data, 'view', $response->get_links() ); 721 $this->check_post_data( $attachment, $data, 'embed', $response->get_links() ); 722 } 723 724 public function test_get_item_schema() { 725 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' ); 726 $response = $this->server->dispatch( $request ); 727 $data = $response->get_data(); 728 $properties = $data['schema']['properties']; 729 $this->assertEquals( 23, count( $properties ) ); 730 $this->assertArrayHasKey( 'author', $properties ); 731 $this->assertArrayHasKey( 'alt_text', $properties ); 732 $this->assertArrayHasKey( 'caption', $properties ); 733 $this->assertArrayHasKey( 'description', $properties ); 734 $this->assertArrayHasKey( 'comment_status', $properties ); 735 $this->assertArrayHasKey( 'date', $properties ); 736 $this->assertArrayHasKey( 'date_gmt', $properties ); 737 $this->assertArrayHasKey( 'guid', $properties ); 738 $this->assertArrayHasKey( 'id', $properties ); 739 $this->assertArrayHasKey( 'link', $properties ); 740 $this->assertArrayHasKey( 'media_type', $properties ); 741 $this->assertArrayHasKey( 'meta', $properties ); 742 $this->assertArrayHasKey( 'mime_type', $properties ); 743 $this->assertArrayHasKey( 'media_details', $properties ); 744 $this->assertArrayHasKey( 'modified', $properties ); 745 $this->assertArrayHasKey( 'modified_gmt', $properties ); 746 $this->assertArrayHasKey( 'post', $properties ); 747 $this->assertArrayHasKey( 'ping_status', $properties ); 748 $this->assertArrayHasKey( 'status', $properties ); 749 $this->assertArrayHasKey( 'slug', $properties ); 750 $this->assertArrayHasKey( 'source_url', $properties ); 751 $this->assertArrayHasKey( 'title', $properties ); 752 $this->assertArrayHasKey( 'type', $properties ); 753 } 754 755 public function test_get_additional_field_registration() { 756 757 $schema = array( 758 'type' => 'integer', 759 'description' => 'Some integer of mine', 760 'enum' => array( 1, 2, 3, 4 ), 761 'context' => array( 'view', 'edit' ), 762 ); 763 764 register_rest_field( 'attachment', 'my_custom_int', array( 765 'schema' => $schema, 766 'get_callback' => array( $this, 'additional_field_get_callback' ), 767 ) ); 768 769 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' ); 770 771 $response = $this->server->dispatch( $request ); 772 $data = $response->get_data(); 773 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 774 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 775 776 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 777 'post_mime_type' => 'image/jpeg', 778 'post_excerpt' => 'A sample caption', 779 ) ); 780 781 $request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id ); 782 783 $response = $this->server->dispatch( $request ); 784 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 785 786 global $wp_rest_additional_fields; 787 $wp_rest_additional_fields = array(); 788 } 789 790 public function test_additional_field_update_errors() { 791 $schema = array( 792 'type' => 'integer', 793 'description' => 'Some integer of mine', 794 'enum' => array( 1, 2, 3, 4 ), 795 'context' => array( 'view', 'edit' ), 796 ); 797 798 register_rest_field( 'attachment', 'my_custom_int', array( 799 'schema' => $schema, 800 'get_callback' => array( $this, 'additional_field_get_callback' ), 801 'update_callback' => array( $this, 'additional_field_update_callback' ), 802 ) ); 803 804 wp_set_current_user( $this->editor_id ); 805 $attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array( 806 'post_mime_type' => 'image/jpeg', 807 'post_excerpt' => 'A sample caption', 808 'post_author' => $this->editor_id, 809 ) ); 810 // Check for error on update. 811 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/media/%d', $attachment_id ) ); 812 $request->set_body_params(array( 813 'my_custom_int' => 'returnError', 814 )); 815 816 $response = $this->server->dispatch( $request ); 817 818 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 819 820 global $wp_rest_additional_fields; 821 $wp_rest_additional_fields = array(); 822 } 823 824 public function additional_field_get_callback( $object, $request ) { 825 return 123; 826 } 827 828 public function additional_field_update_callback( $value, $attachment ) { 829 if ( 'returnError' === $value ) { 830 return new WP_Error( 'rest_invalid_param', 'Testing an error.', array( 'status' => 400 ) ); 831 } 832 } 833 834 public function tearDown() { 835 parent::tearDown(); 836 if ( file_exists( $this->test_file ) ) { 837 unlink( $this->test_file ); 838 } 839 if ( file_exists( $this->test_file2 ) ) { 840 unlink( $this->test_file2 ); 841 } 842 } 843 844 protected function check_post_data( $attachment, $data, $context = 'view', $links ) { 845 parent::check_post_data( $attachment, $data, $context, $links ); 846 847 $this->assertEquals( get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ), $data['alt_text'] ); 848 $this->assertEquals( $attachment->post_excerpt, $data['caption'] ); 849 $this->assertEquals( $attachment->post_content, $data['description'] ); 850 $this->assertTrue( isset( $data['media_details'] ) ); 851 852 if ( $attachment->post_parent ) { 853 $this->assertEquals( $attachment->post_parent, $data['post'] ); 854 } else { 855 $this->assertNull( $data['post'] ); 856 } 857 858 $this->assertEquals( wp_get_attachment_url( $attachment->ID ), $data['source_url'] ); 859 860 } 861 862 } -
tests/phpunit/tests/rest-api/rest-categories-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Terms_Controller functionality, used for 4 * Categories. 5 * 6 * @package WordPress 7 * @subpackage REST API 8 */ 9 10 /** 11 * @group restapi 12 */ 13 class WP_Test_REST_Categories_Controller extends WP_Test_REST_Controller_Testcase { 14 15 public function setUp() { 16 parent::setUp(); 17 $this->administrator = $this->factory->user->create( array( 18 'role' => 'administrator', 19 ) ); 20 $this->subscriber = $this->factory->user->create( array( 21 'role' => 'subscriber', 22 ) ); 23 } 24 25 public function test_register_routes() { 26 $routes = $this->server->get_routes(); 27 $this->assertArrayHasKey( '/wp/v2/categories', $routes ); 28 $this->assertArrayHasKey( '/wp/v2/categories/(?P<id>[\d]+)', $routes ); 29 } 30 31 public function test_context_param() { 32 // Collection 33 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories' ); 34 $response = $this->server->dispatch( $request ); 35 $data = $response->get_data(); 36 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 37 $this->assertEqualSets( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 38 // Single 39 $category1 = $this->factory->category->create( array( 'name' => 'Season 5' ) ); 40 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories/' . $category1 ); 41 $response = $this->server->dispatch( $request ); 42 $data = $response->get_data(); 43 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 44 $this->assertEqualSets( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 45 } 46 47 public function test_registered_query_params() { 48 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories' ); 49 $response = $this->server->dispatch( $request ); 50 $data = $response->get_data(); 51 $keys = array_keys( $data['endpoints'][0]['args'] ); 52 sort( $keys ); 53 $this->assertEquals( array( 54 'context', 55 'exclude', 56 'hide_empty', 57 'include', 58 'order', 59 'orderby', 60 'page', 61 'parent', 62 'per_page', 63 'post', 64 'search', 65 'slug', 66 ), $keys ); 67 } 68 69 public function test_get_items() { 70 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 71 $response = $this->server->dispatch( $request ); 72 $this->check_get_taxonomy_terms_response( $response ); 73 } 74 75 public function test_get_items_invalid_permission_for_context() { 76 wp_set_current_user( 0 ); 77 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 78 $request->set_param( 'context', 'edit' ); 79 $response = $this->server->dispatch( $request ); 80 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 81 } 82 83 public function test_get_items_hide_empty_arg() { 84 $post_id = $this->factory->post->create(); 85 $category1 = $this->factory->category->create( array( 'name' => 'Season 5' ) ); 86 $category2 = $this->factory->category->create( array( 'name' => 'The Be Sharps' ) ); 87 wp_set_object_terms( $post_id, array( $category1, $category2 ), 'category' ); 88 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 89 $request->set_param( 'hide_empty', true ); 90 $response = $this->server->dispatch( $request ); 91 $data = $response->get_data(); 92 $this->assertEquals( 2, count( $data ) ); 93 $this->assertEquals( 'Season 5', $data[0]['name'] ); 94 $this->assertEquals( 'The Be Sharps', $data[1]['name'] ); 95 } 96 97 public function test_get_items_parent_zero_arg() { 98 $parent1 = $this->factory->category->create( array( 'name' => 'Homer' ) ); 99 $parent2 = $this->factory->category->create( array( 'name' => 'Marge' ) ); 100 $this->factory->category->create( 101 array( 102 'name' => 'Bart', 103 'parent' => $parent1, 104 ) 105 ); 106 $this->factory->category->create( 107 array( 108 'name' => 'Lisa', 109 'parent' => $parent2, 110 ) 111 ); 112 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 113 $request->set_param( 'parent', 0 ); 114 $response = $this->server->dispatch( $request ); 115 116 $this->assertEquals( 200, $response->get_status() ); 117 $data = $response->get_data(); 118 119 $args = array( 120 'hide_empty' => false, 121 'parent' => 0, 122 ); 123 $categories = get_terms( 'category', $args ); 124 $this->assertEquals( count( $categories ), count( $data ) ); 125 } 126 127 public function test_get_items_parent_zero_arg_string() { 128 $parent1 = $this->factory->category->create( array( 'name' => 'Homer' ) ); 129 $parent2 = $this->factory->category->create( array( 'name' => 'Marge' ) ); 130 $this->factory->category->create( 131 array( 132 'name' => 'Bart', 133 'parent' => $parent1, 134 ) 135 ); 136 $this->factory->category->create( 137 array( 138 'name' => 'Lisa', 139 'parent' => $parent2, 140 ) 141 ); 142 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 143 $request->set_param( 'parent', '0' ); 144 $response = $this->server->dispatch( $request ); 145 146 $this->assertEquals( 200, $response->get_status() ); 147 $data = $response->get_data(); 148 149 $args = array( 150 'hide_empty' => false, 151 'parent' => 0, 152 ); 153 $categories = get_terms( 'category', $args ); 154 $this->assertEquals( count( $categories ), count( $data ) ); 155 } 156 157 public function test_get_items_by_parent_non_found() { 158 $parent1 = $this->factory->category->create( array( 'name' => 'Homer' ) ); 159 160 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 161 $request->set_param( 'parent', $parent1 ); 162 $response = $this->server->dispatch( $request ); 163 164 $this->assertEquals( 200, $response->get_status() ); 165 $data = $response->get_data(); 166 167 $this->assertEquals( array(), $data ); 168 } 169 170 public function test_get_items_invalid_page() { 171 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 172 $request->set_param( 'page', 0 ); 173 $response = $this->server->dispatch( $request ); 174 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 175 $data = $response->get_data(); 176 $first_error = array_shift( $data['data']['params'] ); 177 $this->assertContains( 'page must be greater than 1 (inclusive)', $first_error ); 178 } 179 180 public function test_get_items_include_query() { 181 $id1 = $this->factory->category->create(); 182 $this->factory->category->create(); 183 $id3 = $this->factory->category->create(); 184 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 185 // Orderby=>asc 186 $request->set_param( 'include', array( $id3, $id1 ) ); 187 $response = $this->server->dispatch( $request ); 188 $data = $response->get_data(); 189 $this->assertEquals( 2, count( $data ) ); 190 $this->assertEquals( $id1, $data[0]['id'] ); 191 // Orderby=>include 192 $request->set_param( 'orderby', 'include' ); 193 $response = $this->server->dispatch( $request ); 194 $data = $response->get_data(); 195 $this->assertEquals( 2, count( $data ) ); 196 $this->assertEquals( $id3, $data[0]['id'] ); 197 } 198 199 public function test_get_items_exclude_query() { 200 $id1 = $this->factory->category->create(); 201 $id2 = $this->factory->category->create(); 202 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 203 $response = $this->server->dispatch( $request ); 204 $data = $response->get_data(); 205 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 206 $this->assertTrue( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 207 $request->set_param( 'exclude', array( $id2 ) ); 208 $response = $this->server->dispatch( $request ); 209 $data = $response->get_data(); 210 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 211 $this->assertFalse( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 212 } 213 214 public function test_get_items_orderby_args() { 215 $this->factory->category->create( array( 'name' => 'Apple' ) ); 216 $this->factory->category->create( array( 'name' => 'Banana' ) ); 217 /* 218 * Tests: 219 * - orderby 220 * - order 221 * - per_page 222 */ 223 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 224 $request->set_param( 'orderby', 'name' ); 225 $request->set_param( 'order', 'desc' ); 226 $request->set_param( 'per_page', 1 ); 227 $response = $this->server->dispatch( $request ); 228 $this->assertEquals( 200, $response->get_status() ); 229 $data = $response->get_data(); 230 $this->assertEquals( 1, count( $data ) ); 231 $this->assertEquals( 'Uncategorized', $data[0]['name'] ); 232 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 233 $request->set_param( 'orderby', 'name' ); 234 $request->set_param( 'order', 'asc' ); 235 $request->set_param( 'per_page', 2 ); 236 $response = $this->server->dispatch( $request ); 237 $this->assertEquals( 200, $response->get_status() ); 238 $data = $response->get_data(); 239 $this->assertEquals( 2, count( $data ) ); 240 $this->assertEquals( 'Apple', $data[0]['name'] ); 241 } 242 243 public function test_get_items_orderby_id() { 244 $this->factory->category->create( array( 'name' => 'Cantaloupe' ) ); 245 $this->factory->category->create( array( 'name' => 'Apple' ) ); 246 $this->factory->category->create( array( 'name' => 'Banana' ) ); 247 // defaults to orderby=name, order=asc 248 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 249 $response = $this->server->dispatch( $request ); 250 $this->assertEquals( 200, $response->get_status() ); 251 $data = $response->get_data(); 252 $this->assertEquals( 'Apple', $data[0]['name'] ); 253 $this->assertEquals( 'Banana', $data[1]['name'] ); 254 $this->assertEquals( 'Cantaloupe', $data[2]['name'] ); 255 $this->assertEquals( 'Uncategorized', $data[3]['name'] ); 256 // orderby=id, with default order=asc 257 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 258 $request->set_param( 'orderby', 'id' ); 259 $response = $this->server->dispatch( $request ); 260 $this->assertEquals( 200, $response->get_status() ); 261 $data = $response->get_data(); 262 $this->assertEquals( 'Uncategorized', $data[0]['name'] ); 263 $this->assertEquals( 'Cantaloupe', $data[1]['name'] ); 264 $this->assertEquals( 'Apple', $data[2]['name'] ); 265 $this->assertEquals( 'Banana', $data[3]['name'] ); 266 // orderby=id, order=desc 267 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 268 $request->set_param( 'orderby', 'id' ); 269 $request->set_param( 'order', 'desc' ); 270 $response = $this->server->dispatch( $request ); 271 $data = $response->get_data(); 272 $this->assertEquals( 200, $response->get_status() ); 273 $this->assertEquals( 'Banana', $data[0]['name'] ); 274 $this->assertEquals( 'Apple', $data[1]['name'] ); 275 $this->assertEquals( 'Cantaloupe', $data[2]['name'] ); 276 } 277 278 protected function post_with_categories() { 279 $post_id = $this->factory->post->create(); 280 $category1 = $this->factory->category->create( array( 281 'name' => 'DC', 282 'description' => 'Purveyor of fine detective comics', 283 ) ); 284 $category2 = $this->factory->category->create( array( 285 'name' => 'Marvel', 286 'description' => 'Home of the Marvel Universe', 287 ) ); 288 $category3 = $this->factory->category->create( array( 289 'name' => 'Image', 290 'description' => 'American independent comic publisher', 291 ) ); 292 wp_set_object_terms( $post_id, array( $category1, $category2, $category3 ), 'category' ); 293 294 return $post_id; 295 } 296 297 public function test_get_items_post_args() { 298 $post_id = $this->post_with_categories(); 299 300 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 301 $request->set_param( 'post', $post_id ); 302 $response = $this->server->dispatch( $request ); 303 $this->assertEquals( 200, $response->get_status() ); 304 305 $data = $response->get_data(); 306 $this->assertEquals( 3, count( $data ) ); 307 308 // Check ordered by name by default 309 $names = wp_list_pluck( $data, 'name' ); 310 $this->assertEquals( array( 'DC', 'Image', 'Marvel' ), $names ); 311 } 312 313 public function test_get_items_post_ordered_by_description() { 314 $post_id = $this->post_with_categories(); 315 316 // Regular request 317 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 318 $request->set_param( 'post', $post_id ); 319 $request->set_param( 'orderby', 'description' ); 320 $response = $this->server->dispatch( $request ); 321 $this->assertEquals( 200, $response->get_status() ); 322 323 $data = $response->get_data(); 324 $this->assertEquals( 3, count( $data ) ); 325 $names = wp_list_pluck( $data, 'name' ); 326 $this->assertEquals( array( 'Image', 'Marvel', 'DC' ), $names, 'Terms should be ordered by description' ); 327 328 // Flip the order 329 $request->set_param( 'order', 'desc' ); 330 $response = $this->server->dispatch( $request ); 331 $this->assertEquals( 200, $response->get_status() ); 332 333 $data = $response->get_data(); 334 $this->assertEquals( 3, count( $data ) ); 335 $names = wp_list_pluck( $data, 'name' ); 336 $this->assertEquals( array( 'DC', 'Marvel', 'Image' ), $names, 'Terms should be reverse-ordered by description' ); 337 } 338 339 public function test_get_items_post_ordered_by_id() { 340 $post_id = $this->post_with_categories(); 341 342 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 343 $request->set_param( 'post', $post_id ); 344 $request->set_param( 'orderby', 'id' ); 345 $response = $this->server->dispatch( $request ); 346 $this->assertEquals( 200, $response->get_status() ); 347 348 $data = $response->get_data(); 349 $this->assertEquals( 3, count( $data ) ); 350 $names = wp_list_pluck( $data, 'name' ); 351 $this->assertEquals( array( 'DC', 'Marvel', 'Image' ), $names ); 352 } 353 354 public function test_get_items_custom_tax_post_args() { 355 register_taxonomy( 'batman', 'post', array( 'show_in_rest' => true ) ); 356 $controller = new WP_REST_Terms_Controller( 'batman' ); 357 $controller->register_routes(); 358 $term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'batman' ) ); 359 $term2 = $this->factory->term->create( array( 'name' => 'Mask', 'taxonomy' => 'batman' ) ); 360 $this->factory->term->create( array( 'name' => 'Car', 'taxonomy' => 'batman' ) ); 361 $post_id = $this->factory->post->create(); 362 wp_set_object_terms( $post_id, array( $term1, $term2 ), 'batman' ); 363 364 $request = new WP_REST_Request( 'GET', '/wp/v2/batman' ); 365 $request->set_param( 'post', $post_id ); 366 $response = $this->server->dispatch( $request ); 367 $this->assertEquals( 200, $response->get_status() ); 368 369 $data = $response->get_data(); 370 $this->assertEquals( 2, count( $data ) ); 371 $this->assertEquals( 'Cape', $data[0]['name'] ); 372 } 373 374 public function test_get_items_search_args() { 375 $this->factory->category->create( array( 'name' => 'Apple' ) ); 376 $this->factory->category->create( array( 'name' => 'Banana' ) ); 377 /* 378 * Tests: 379 * - search 380 */ 381 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 382 $request->set_param( 'search', 'App' ); 383 $response = $this->server->dispatch( $request ); 384 $this->assertEquals( 200, $response->get_status() ); 385 $data = $response->get_data(); 386 $this->assertEquals( 1, count( $data ) ); 387 $this->assertEquals( 'Apple', $data[0]['name'] ); 388 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 389 $request->set_param( 'search', 'Garbage' ); 390 $response = $this->server->dispatch( $request ); 391 $this->assertEquals( 200, $response->get_status() ); 392 $data = $response->get_data(); 393 $this->assertEquals( 0, count( $data ) ); 394 } 395 396 public function test_get_items_slug_arg() { 397 $this->factory->category->create( array( 'name' => 'Apple' ) ); 398 $this->factory->category->create( array( 'name' => 'Banana' ) ); 399 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 400 $request->set_param( 'slug', 'apple' ); 401 $response = $this->server->dispatch( $request ); 402 $this->assertEquals( 200, $response->get_status() ); 403 $data = $response->get_data(); 404 $this->assertEquals( 1, count( $data ) ); 405 $this->assertEquals( 'Apple', $data[0]['name'] ); 406 } 407 408 public function test_get_terms_parent_arg() { 409 $category1 = $this->factory->category->create( array( 'name' => 'Parent' ) ); 410 $this->factory->category->create( array( 'name' => 'Child', 'parent' => $category1 ) ); 411 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 412 $request->set_param( 'parent', $category1 ); 413 $response = $this->server->dispatch( $request ); 414 $data = $response->get_data(); 415 $this->assertEquals( 1, count( $data ) ); 416 $this->assertEquals( 'Child', $data[0]['name'] ); 417 } 418 419 public function test_get_terms_private_taxonomy() { 420 register_taxonomy( 'robin', 'post', array( 'public' => false ) ); 421 $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) ); 422 $this->factory->term->create( array( 'name' => 'Mask', 'taxonomy' => 'robin' ) ); 423 424 $request = new WP_REST_Request( 'GET', '/wp/v2/terms/robin' ); 425 $response = $this->server->dispatch( $request ); 426 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 427 } 428 429 public function test_get_terms_invalid_taxonomy() { 430 $request = new WP_REST_Request( 'GET', '/wp/v2/invalid-taxonomy' ); 431 $response = $this->server->dispatch( $request ); 432 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 433 } 434 435 public function test_get_terms_pagination_headers() { 436 // Start of the index + Uncategorized default term 437 for ( $i = 0; $i < 49; $i++ ) { 438 $this->factory->category->create( array( 439 'name' => "Category {$i}", 440 ) ); 441 } 442 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 443 $response = $this->server->dispatch( $request ); 444 $headers = $response->get_headers(); 445 $this->assertEquals( 50, $headers['X-WP-Total'] ); 446 $this->assertEquals( 5, $headers['X-WP-TotalPages'] ); 447 $this->assertCount( 10, $response->get_data() ); 448 $next_link = add_query_arg( array( 449 'page' => 2, 450 ), rest_url( 'wp/v2/categories' ) ); 451 $this->assertFalse( stripos( $headers['Link'], 'rel="prev"' ) ); 452 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 453 // 3rd page 454 $this->factory->category->create( array( 455 'name' => 'Category 51', 456 ) ); 457 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 458 $request->set_param( 'page', 3 ); 459 $response = $this->server->dispatch( $request ); 460 $headers = $response->get_headers(); 461 $this->assertEquals( 51, $headers['X-WP-Total'] ); 462 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 463 $this->assertCount( 10, $response->get_data() ); 464 $prev_link = add_query_arg( array( 465 'page' => 2, 466 ), rest_url( 'wp/v2/categories' ) ); 467 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 468 $next_link = add_query_arg( array( 469 'page' => 4, 470 ), rest_url( 'wp/v2/categories' ) ); 471 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 472 // Last page 473 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 474 $request->set_param( 'page', 6 ); 475 $response = $this->server->dispatch( $request ); 476 $headers = $response->get_headers(); 477 $this->assertEquals( 51, $headers['X-WP-Total'] ); 478 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 479 $this->assertCount( 1, $response->get_data() ); 480 $prev_link = add_query_arg( array( 481 'page' => 5, 482 ), rest_url( 'wp/v2/categories' ) ); 483 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 484 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 485 // Out of bounds 486 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 487 $request->set_param( 'page', 8 ); 488 $response = $this->server->dispatch( $request ); 489 $headers = $response->get_headers(); 490 $this->assertEquals( 51, $headers['X-WP-Total'] ); 491 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 492 $this->assertCount( 0, $response->get_data() ); 493 $prev_link = add_query_arg( array( 494 'page' => 6, 495 ), rest_url( 'wp/v2/categories' ) ); 496 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 497 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 498 } 499 500 public function test_get_items_per_page_exceeds_number_of_items() { 501 // Start of the index + Uncategorized default term 502 for ( $i = 0; $i < 17; $i++ ) { 503 $this->factory->category->create( array( 504 'name' => "Category {$i}", 505 ) ); 506 } 507 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 508 $request->set_param( 'page', 1 ); 509 $request->set_param( 'per_page', 100 ); 510 $response = $this->server->dispatch( $request ); 511 $headers = $response->get_headers(); 512 $this->assertEquals( 18, $headers['X-WP-Total'] ); 513 $this->assertEquals( 1, $headers['X-WP-TotalPages'] ); 514 $this->assertCount( 18, $response->get_data() ); 515 $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); 516 $request->set_param( 'page', 2 ); 517 $request->set_param( 'per_page', 100 ); 518 $response = $this->server->dispatch( $request ); 519 $headers = $response->get_headers(); 520 $this->assertEquals( 18, $headers['X-WP-Total'] ); 521 $this->assertEquals( 1, $headers['X-WP-TotalPages'] ); 522 $this->assertCount( 0, $response->get_data() ); 523 } 524 525 public function test_get_item() { 526 $request = new WP_REST_Request( 'GET', '/wp/v2/categories/1' ); 527 $response = $this->server->dispatch( $request ); 528 $this->check_get_taxonomy_term_response( $response ); 529 } 530 531 public function test_get_term_invalid_taxonomy() { 532 $request = new WP_REST_Request( 'GET', '/wp/v2/invalid-taxonomy/1' ); 533 $response = $this->server->dispatch( $request ); 534 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 535 } 536 537 public function test_get_term_invalid_term() { 538 $request = new WP_REST_Request( 'GET', '/wp/v2/categories/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 539 $response = $this->server->dispatch( $request ); 540 $this->assertErrorResponse( 'rest_term_invalid', $response, 404 ); 541 } 542 543 public function test_get_item_invalid_permission_for_context() { 544 wp_set_current_user( 0 ); 545 $request = new WP_REST_Request( 'GET', '/wp/v2/categories/1' ); 546 $request->set_param( 'context', 'edit' ); 547 $response = $this->server->dispatch( $request ); 548 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 549 } 550 551 public function test_get_term_private_taxonomy() { 552 register_taxonomy( 'robin', 'post', array( 'public' => false ) ); 553 $term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) ); 554 555 $request = new WP_REST_Request( 'GET', '/wp/v2/terms/robin/' . $term1 ); 556 $response = $this->server->dispatch( $request ); 557 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 558 } 559 560 public function test_get_item_incorrect_taxonomy() { 561 register_taxonomy( 'robin', 'post' ); 562 $term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) ); 563 $request = new WP_REST_Request( 'GET', '/wp/v2/categories/' . $term1 ); 564 $response = $this->server->dispatch( $request ); 565 $this->assertErrorResponse( 'rest_term_invalid', $response, 404 ); 566 } 567 568 public function test_create_item() { 569 wp_set_current_user( $this->administrator ); 570 $request = new WP_REST_Request( 'POST', '/wp/v2/categories' ); 571 $request->set_param( 'name', 'My Awesome Term' ); 572 $request->set_param( 'description', 'This term is so awesome.' ); 573 $request->set_param( 'slug', 'so-awesome' ); 574 $response = $this->server->dispatch( $request ); 575 $this->assertEquals( 201, $response->get_status() ); 576 $headers = $response->get_headers(); 577 $data = $response->get_data(); 578 $this->assertContains( '/wp/v2/categories/' . $data['id'], $headers['Location'] ); 579 $this->assertEquals( 'My Awesome Term', $data['name'] ); 580 $this->assertEquals( 'This term is so awesome.', $data['description'] ); 581 $this->assertEquals( 'so-awesome', $data['slug'] ); 582 } 583 584 public function test_create_item_invalid_taxonomy() { 585 wp_set_current_user( $this->administrator ); 586 $request = new WP_REST_Request( 'POST', '/wp/v2/invalid-taxonomy' ); 587 $request->set_param( 'name', 'Invalid Taxonomy' ); 588 $response = $this->server->dispatch( $request ); 589 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 590 } 591 592 public function test_create_item_incorrect_permissions() { 593 wp_set_current_user( $this->subscriber ); 594 $request = new WP_REST_Request( 'POST', '/wp/v2/categories' ); 595 $request->set_param( 'name', 'Incorrect permissions' ); 596 $response = $this->server->dispatch( $request ); 597 $this->assertErrorResponse( 'rest_cannot_create', $response, 403 ); 598 } 599 600 public function test_create_item_missing_arguments() { 601 wp_set_current_user( $this->administrator ); 602 $request = new WP_REST_Request( 'POST', '/wp/v2/categories' ); 603 $response = $this->server->dispatch( $request ); 604 $this->assertErrorResponse( 'rest_missing_callback_param', $response, 400 ); 605 } 606 607 public function test_create_item_with_parent() { 608 wp_set_current_user( $this->administrator ); 609 $parent = wp_insert_term( 'test-category', 'category' ); 610 $request = new WP_REST_Request( 'POST', '/wp/v2/categories' ); 611 $request->set_param( 'name', 'My Awesome Term' ); 612 $request->set_param( 'parent', $parent['term_id'] ); 613 $response = $this->server->dispatch( $request ); 614 $this->assertEquals( 201, $response->get_status() ); 615 $data = $response->get_data(); 616 $this->assertEquals( $parent['term_id'], $data['parent'] ); 617 } 618 619 public function test_create_item_invalid_parent() { 620 wp_set_current_user( $this->administrator ); 621 $term = get_term_by( 'id', $this->factory->category->create(), 'category' ); 622 623 $request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_id ); 624 $request->set_param( 'name', 'My Awesome Term' ); 625 $request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 626 $response = $this->server->dispatch( $request ); 627 $this->assertErrorResponse( 'rest_term_invalid', $response, 400 ); 628 } 629 630 public function test_update_item() { 631 wp_set_current_user( $this->administrator ); 632 $orig_args = array( 633 'name' => 'Original Name', 634 'description' => 'Original Description', 635 'slug' => 'original-slug', 636 ); 637 $term = get_term_by( 'id', $this->factory->category->create( $orig_args ), 'category' ); 638 $request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_id ); 639 $request->set_param( 'name', 'New Name' ); 640 $request->set_param( 'description', 'New Description' ); 641 $request->set_param( 'slug', 'new-slug' ); 642 $response = $this->server->dispatch( $request ); 643 $this->assertEquals( 200, $response->get_status() ); 644 $data = $response->get_data(); 645 $this->assertEquals( 'New Name', $data['name'] ); 646 $this->assertEquals( 'New Description', $data['description'] ); 647 $this->assertEquals( 'new-slug', $data['slug'] ); 648 } 649 650 public function test_update_item_invalid_taxonomy() { 651 wp_set_current_user( $this->administrator ); 652 $request = new WP_REST_Request( 'POST', '/wp/v2/invalid-taxonomy/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 653 $request->set_param( 'name', 'Invalid Taxonomy' ); 654 $response = $this->server->dispatch( $request ); 655 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 656 } 657 658 public function test_update_item_invalid_term() { 659 wp_set_current_user( $this->administrator ); 660 $request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 661 $request->set_param( 'name', 'Invalid Term' ); 662 $response = $this->server->dispatch( $request ); 663 $this->assertErrorResponse( 'rest_term_invalid', $response, 404 ); 664 } 665 666 public function test_update_item_incorrect_permissions() { 667 wp_set_current_user( $this->subscriber ); 668 $term = get_term_by( 'id', $this->factory->category->create(), 'category' ); 669 $request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_id ); 670 $request->set_param( 'name', 'Incorrect permissions' ); 671 $response = $this->server->dispatch( $request ); 672 $this->assertErrorResponse( 'rest_cannot_update', $response, 403 ); 673 } 674 675 public function test_update_item_parent() { 676 wp_set_current_user( $this->administrator ); 677 $parent = get_term_by( 'id', $this->factory->category->create(), 'category' ); 678 $term = get_term_by( 'id', $this->factory->category->create(), 'category' ); 679 680 $request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_taxonomy_id ); 681 $request->set_param( 'parent', $parent->term_id ); 682 $response = $this->server->dispatch( $request ); 683 $this->assertEquals( 200, $response->get_status() ); 684 685 $data = $response->get_data(); 686 $this->assertEquals( $parent->term_id, $data['parent'] ); 687 } 688 689 public function test_update_item_invalid_parent() { 690 wp_set_current_user( $this->administrator ); 691 $term = get_term_by( 'id', $this->factory->category->create(), 'category' ); 692 693 $request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_id ); 694 $request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 695 $response = $this->server->dispatch( $request ); 696 $this->assertErrorResponse( 'rest_term_invalid', $response, 400 ); 697 } 698 699 public function test_delete_item() { 700 wp_set_current_user( $this->administrator ); 701 $term = get_term_by( 'id', $this->factory->category->create( array( 'name' => 'Deleted Category' ) ), 'category' ); 702 $request = new WP_REST_Request( 'DELETE', '/wp/v2/categories/' . $term->term_id ); 703 $request->set_param( 'force', true ); 704 $response = $this->server->dispatch( $request ); 705 $this->assertEquals( 200, $response->get_status() ); 706 $data = $response->get_data(); 707 $this->assertEquals( 'Deleted Category', $data['name'] ); 708 } 709 710 public function test_delete_item_force_false() { 711 wp_set_current_user( $this->administrator ); 712 $term = get_term_by( 'id', $this->factory->category->create( array( 'name' => 'Deleted Category' ) ), 'category' ); 713 $request = new WP_REST_Request( 'DELETE', '/wp/v2/categories/' . $term->term_id ); 714 // force defaults to false 715 $response = $this->server->dispatch( $request ); 716 $this->assertEquals( 501, $response->get_status() ); 717 } 718 719 public function test_delete_item_invalid_taxonomy() { 720 wp_set_current_user( $this->administrator ); 721 $request = new WP_REST_Request( 'DELETE', '/wp/v2/invalid-taxonomy/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 722 $response = $this->server->dispatch( $request ); 723 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 724 } 725 726 public function test_delete_item_invalid_term() { 727 wp_set_current_user( $this->administrator ); 728 $request = new WP_REST_Request( 'DELETE', '/wp/v2/categories/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 729 $response = $this->server->dispatch( $request ); 730 $this->assertErrorResponse( 'rest_term_invalid', $response, 404 ); 731 } 732 733 public function test_delete_item_incorrect_permissions() { 734 wp_set_current_user( $this->subscriber ); 735 $term = get_term_by( 'id', $this->factory->category->create(), 'category' ); 736 $request = new WP_REST_Request( 'DELETE', '/wp/v2/categories/' . $term->term_id ); 737 $response = $this->server->dispatch( $request ); 738 $this->assertErrorResponse( 'rest_cannot_delete', $response, 403 ); 739 } 740 741 public function test_prepare_item() { 742 $term = get_term( 1, 'category' ); 743 744 $request = new WP_REST_Request( 'GET', '/wp/v2/categories/1' ); 745 $response = $this->server->dispatch( $request ); 746 $data = $response->get_data(); 747 748 $this->check_taxonomy_term( $term, $data, $response->get_links() ); 749 } 750 751 public function test_prepare_taxonomy_term_child() { 752 $child = $this->factory->category->create( array( 753 'parent' => 1, 754 ) ); 755 $term = get_term( $child, 'category' ); 756 757 $request = new WP_REST_Request( 'GET', '/wp/v2/categories/' . $child ); 758 $response = $this->server->dispatch( $request ); 759 $data = $response->get_data(); 760 761 $this->check_taxonomy_term( $term, $data, $response->get_links() ); 762 763 $this->assertEquals( 1, $data['parent'] ); 764 765 $links = $response->get_links(); 766 $this->assertEquals( rest_url( 'wp/v2/categories/1' ), $links['up'][0]['href'] ); 767 } 768 769 public function test_get_item_schema() { 770 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories' ); 771 $response = $this->server->dispatch( $request ); 772 $data = $response->get_data(); 773 $properties = $data['schema']['properties']; 774 $this->assertEquals( 9, count( $properties ) ); 775 $this->assertArrayHasKey( 'id', $properties ); 776 $this->assertArrayHasKey( 'count', $properties ); 777 $this->assertArrayHasKey( 'description', $properties ); 778 $this->assertArrayHasKey( 'link', $properties ); 779 $this->assertArrayHasKey( 'meta', $properties ); 780 $this->assertArrayHasKey( 'name', $properties ); 781 $this->assertArrayHasKey( 'parent', $properties ); 782 $this->assertArrayHasKey( 'slug', $properties ); 783 $this->assertArrayHasKey( 'taxonomy', $properties ); 784 $this->assertEquals( array_keys( get_taxonomies() ), $properties['taxonomy']['enum'] ); 785 } 786 787 public function test_get_additional_field_registration() { 788 789 $schema = array( 790 'type' => 'integer', 791 'description' => 'Some integer of mine', 792 'enum' => array( 1, 2, 3, 4 ), 793 'context' => array( 'view', 'edit' ), 794 ); 795 796 register_rest_field( 'category', 'my_custom_int', array( 797 'schema' => $schema, 798 'get_callback' => array( $this, 'additional_field_get_callback' ), 799 ) ); 800 801 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories' ); 802 803 $response = $this->server->dispatch( $request ); 804 $data = $response->get_data(); 805 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 806 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 807 808 $category_id = $this->factory->category->create(); 809 $request = new WP_REST_Request( 'GET', '/wp/v2/categories/' . $category_id ); 810 811 $response = $this->server->dispatch( $request ); 812 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 813 814 global $wp_rest_additional_fields; 815 $wp_rest_additional_fields = array(); 816 } 817 818 public function additional_field_get_callback( $object, $request ) { 819 return 123; 820 } 821 822 public function tearDown() { 823 _unregister_taxonomy( 'batman' ); 824 _unregister_taxonomy( 'robin' ); 825 parent::tearDown(); 826 } 827 828 protected function check_get_taxonomy_terms_response( $response ) { 829 $this->assertEquals( 200, $response->get_status() ); 830 $data = $response->get_data(); 831 $args = array( 832 'hide_empty' => false, 833 ); 834 $categories = get_terms( 'category', $args ); 835 $this->assertEquals( count( $categories ), count( $data ) ); 836 $this->assertEquals( $categories[0]->term_id, $data[0]['id'] ); 837 $this->assertEquals( $categories[0]->name, $data[0]['name'] ); 838 $this->assertEquals( $categories[0]->slug, $data[0]['slug'] ); 839 $this->assertEquals( $categories[0]->taxonomy, $data[0]['taxonomy'] ); 840 $this->assertEquals( $categories[0]->description, $data[0]['description'] ); 841 $this->assertEquals( $categories[0]->count, $data[0]['count'] ); 842 } 843 844 protected function check_taxonomy_term( $term, $data, $links ) { 845 $this->assertEquals( $term->term_id, $data['id'] ); 846 $this->assertEquals( $term->name, $data['name'] ); 847 $this->assertEquals( $term->slug, $data['slug'] ); 848 $this->assertEquals( $term->description, $data['description'] ); 849 $this->assertEquals( get_term_link( $term ), $data['link'] ); 850 $this->assertEquals( $term->count, $data['count'] ); 851 $taxonomy = get_taxonomy( $term->taxonomy ); 852 if ( $taxonomy->hierarchical ) { 853 $this->assertEquals( $term->parent, $data['parent'] ); 854 } else { 855 $this->assertFalse( isset( $term->parent ) ); 856 } 857 858 $relations = array( 859 'self', 860 'collection', 861 'about', 862 'https://api.w.org/post_type', 863 ); 864 865 if ( ! empty( $data['parent'] ) ) { 866 $relations[] = 'up'; 867 } 868 869 $this->assertEqualSets( $relations, array_keys( $links ) ); 870 $this->assertContains( 'wp/v2/taxonomies/' . $term->taxonomy, $links['about'][0]['href'] ); 871 $this->assertEquals( add_query_arg( 'categories', $term->term_id, rest_url( 'wp/v2/posts' ) ), $links['https://api.w.org/post_type'][0]['href'] ); 872 } 873 874 protected function check_get_taxonomy_term_response( $response ) { 875 876 $this->assertEquals( 200, $response->get_status() ); 877 878 $data = $response->get_data(); 879 $category = get_term( 1, 'category' ); 880 $this->check_taxonomy_term( $category, $data, $response->get_links() ); 881 } 882 } -
tests/phpunit/tests/rest-api/rest-comments-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Comments_Controller functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase { 13 14 protected $admin_id; 15 protected $subscriber_id; 16 17 protected $post_id; 18 protected $private_id; 19 protected $draft_id; 20 protected $trash_id; 21 22 protected $approved_id; 23 protected $hold_id; 24 25 protected $endpoint; 26 27 public function setUp() { 28 parent::setUp(); 29 30 $this->admin_id = $this->factory->user->create( array( 31 'role' => 'administrator', 32 )); 33 $this->subscriber_id = $this->factory->user->create( array( 34 'role' => 'subscriber', 35 )); 36 $this->author_id = $this->factory->user->create( array( 37 'role' => 'author', 38 'display_name' => 'Sea Captain', 39 'first_name' => 'Horatio', 40 'last_name' => 'McCallister', 41 'user_email' => 'captain@thefryingdutchman.com', 42 'user_url' => 'http://thefryingdutchman.com', 43 )); 44 45 $this->post_id = $this->factory->post->create(); 46 $this->private_id = $this->factory->post->create( array( 47 'post_status' => 'private', 48 )); 49 $this->draft_id = $this->factory->post->create( array( 50 'post_status' => 'draft', 51 )); 52 $this->trash_id = $this->factory->post->create( array( 53 'post_status' => 'trash', 54 )); 55 56 $this->approved_id = $this->factory->comment->create( array( 57 'comment_approved' => 1, 58 'comment_post_ID' => $this->post_id, 59 'user_id' => 0, 60 )); 61 $this->hold_id = $this->factory->comment->create( array( 62 'comment_approved' => 0, 63 'comment_post_ID' => $this->post_id, 64 'user_id' => $this->subscriber_id, 65 )); 66 67 $this->endpoint = new WP_REST_Comments_Controller; 68 } 69 70 public function tearDown() { 71 parent::tearDown(); 72 } 73 74 public function test_register_routes() { 75 $routes = $this->server->get_routes(); 76 77 $this->assertArrayHasKey( '/wp/v2/comments', $routes ); 78 $this->assertCount( 2, $routes['/wp/v2/comments'] ); 79 $this->assertArrayHasKey( '/wp/v2/comments/(?P<id>[\d]+)', $routes ); 80 $this->assertCount( 3, $routes['/wp/v2/comments/(?P<id>[\d]+)'] ); 81 } 82 83 public function test_context_param() { 84 // Collection 85 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/comments' ); 86 $response = $this->server->dispatch( $request ); 87 $data = $response->get_data(); 88 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 89 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 90 // Single 91 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/comments/' . $this->approved_id ); 92 $response = $this->server->dispatch( $request ); 93 $data = $response->get_data(); 94 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 95 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 96 } 97 98 public function test_registered_query_params() { 99 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/comments' ); 100 $response = $this->server->dispatch( $request ); 101 $data = $response->get_data(); 102 $keys = array_keys( $data['endpoints'][0]['args'] ); 103 sort( $keys ); 104 $this->assertEquals( array( 105 'after', 106 'author', 107 'author_email', 108 'author_exclude', 109 'before', 110 'context', 111 'exclude', 112 'include', 113 'karma', 114 'offset', 115 'order', 116 'orderby', 117 'page', 118 'parent', 119 'parent_exclude', 120 'per_page', 121 'post', 122 'search', 123 'status', 124 'type', 125 ), $keys ); 126 } 127 128 public function test_get_items() { 129 $this->factory->comment->create_post_comments( $this->post_id, 6 ); 130 131 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 132 133 $response = $this->server->dispatch( $request ); 134 $this->assertEquals( 200, $response->get_status() ); 135 136 $comments = $response->get_data(); 137 // We created 6 comments in this method, plus $this->approved_id. 138 $this->assertCount( 7, $comments ); 139 } 140 141 public function test_get_items_without_private_post_permission() { 142 wp_set_current_user( 0 ); 143 144 $args = array( 145 'comment_approved' => 1, 146 'comment_post_ID' => $this->private_id, 147 ); 148 $private_comment = $this->factory->comment->create( $args ); 149 150 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 151 152 $response = $this->server->dispatch( $request ); 153 $this->assertEquals( 200, $response->get_status() ); 154 155 $collection_data = $response->get_data(); 156 $this->assertFalse( in_array( $private_comment, wp_list_pluck( $collection_data, 'id' ), true ) ); 157 } 158 159 public function test_get_items_with_private_post_permission() { 160 wp_set_current_user( $this->admin_id ); 161 162 $args = array( 163 'comment_approved' => 1, 164 'comment_post_ID' => $this->private_id, 165 ); 166 $private_comment = $this->factory->comment->create( $args ); 167 168 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 169 170 $response = $this->server->dispatch( $request ); 171 $this->assertEquals( 200, $response->get_status() ); 172 173 $collection_data = $response->get_data(); 174 $this->assertTrue( in_array( $private_comment, wp_list_pluck( $collection_data, 'id' ), true ) ); 175 } 176 177 public function test_get_items_with_invalid_post() { 178 wp_set_current_user( 0 ); 179 180 $comment_id = $this->factory->comment->create( array( 181 'comment_approved' => 1, 182 'comment_post_ID' => REST_TESTS_IMPOSSIBLY_HIGH_NUMBER, 183 )); 184 185 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 186 187 $response = $this->server->dispatch( $request ); 188 $this->assertEquals( 200, $response->get_status() ); 189 190 $collection_data = $response->get_data(); 191 $this->assertFalse( in_array( $comment_id, wp_list_pluck( $collection_data, 'id' ), true ) ); 192 193 wp_delete_comment( $comment_id ); 194 } 195 196 public function test_get_items_with_invalid_post_permission() { 197 wp_set_current_user( $this->admin_id ); 198 199 $comment_id = $this->factory->comment->create( array( 200 'comment_approved' => 1, 201 'comment_post_ID' => REST_TESTS_IMPOSSIBLY_HIGH_NUMBER, 202 )); 203 204 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 205 206 $response = $this->server->dispatch( $request ); 207 $this->assertEquals( 200, $response->get_status() ); 208 209 $collection_data = $response->get_data(); 210 $this->assertTrue( in_array( $comment_id, wp_list_pluck( $collection_data, 'id' ), true ) ); 211 212 wp_delete_comment( $comment_id ); 213 } 214 215 public function test_get_items_no_permission_for_context() { 216 wp_set_current_user( 0 ); 217 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 218 $request->set_param( 'context', 'edit' ); 219 $response = $this->server->dispatch( $request ); 220 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 221 } 222 223 public function test_get_items_no_post() { 224 $this->factory->comment->create_post_comments( 0, 2 ); 225 wp_set_current_user( $this->admin_id ); 226 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 227 $request->set_param( 'post', 0 ); 228 $response = $this->server->dispatch( $request ); 229 $this->assertEquals( 200, $response->get_status() ); 230 $comments = $response->get_data(); 231 $this->assertCount( 2, $comments ); 232 } 233 234 public function test_get_items_no_permission_for_no_post() { 235 wp_set_current_user( 0 ); 236 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 237 $request->set_param( 'post', 0 ); 238 $response = $this->server->dispatch( $request ); 239 $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); 240 } 241 242 public function test_get_items_edit_context() { 243 wp_set_current_user( $this->admin_id ); 244 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 245 $request->set_param( 'context', 'edit' ); 246 $response = $this->server->dispatch( $request ); 247 $this->assertEquals( 200, $response->get_status() ); 248 } 249 250 public function test_get_items_for_post() { 251 $second_post_id = $this->factory->post->create(); 252 $this->factory->comment->create_post_comments( $second_post_id, 2 ); 253 254 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 255 $request->set_query_params( array( 256 'post' => $second_post_id, 257 ) ); 258 259 $response = $this->server->dispatch( $request ); 260 $this->assertEquals( 200, $response->get_status() ); 261 262 $comments = $response->get_data(); 263 $this->assertCount( 2, $comments ); 264 } 265 266 public function test_get_items_include_query() { 267 wp_set_current_user( $this->admin_id ); 268 $args = array( 269 'comment_approved' => 1, 270 'comment_post_ID' => $this->post_id, 271 ); 272 $id1 = $this->factory->comment->create( $args ); 273 $this->factory->comment->create( $args ); 274 $id3 = $this->factory->comment->create( $args ); 275 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 276 // Order=>asc 277 $request->set_param( 'order', 'asc' ); 278 $request->set_param( 'include', array( $id3, $id1 ) ); 279 $response = $this->server->dispatch( $request ); 280 $data = $response->get_data(); 281 $this->assertEquals( 2, count( $data ) ); 282 $this->assertEquals( $id1, $data[0]['id'] ); 283 // Orderby=>include 284 $request->set_param( 'orderby', 'include' ); 285 $response = $this->server->dispatch( $request ); 286 $data = $response->get_data(); 287 $this->assertEquals( 2, count( $data ) ); 288 $this->assertEquals( $id3, $data[0]['id'] ); 289 } 290 291 public function test_get_items_exclude_query() { 292 wp_set_current_user( $this->admin_id ); 293 $args = array( 294 'comment_approved' => 1, 295 'comment_post_ID' => $this->post_id, 296 ); 297 $id1 = $this->factory->comment->create( $args ); 298 $id2 = $this->factory->comment->create( $args ); 299 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 300 $response = $this->server->dispatch( $request ); 301 $data = $response->get_data(); 302 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 303 $this->assertTrue( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 304 $request->set_param( 'exclude', array( $id2 ) ); 305 $response = $this->server->dispatch( $request ); 306 $data = $response->get_data(); 307 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 308 $this->assertFalse( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 309 } 310 311 public function test_get_items_offset_query() { 312 wp_set_current_user( $this->admin_id ); 313 $args = array( 314 'comment_approved' => 1, 315 'comment_post_ID' => $this->post_id, 316 ); 317 $this->factory->comment->create( $args ); 318 $this->factory->comment->create( $args ); 319 $this->factory->comment->create( $args ); 320 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 321 $request->set_param( 'offset', 1 ); 322 $response = $this->server->dispatch( $request ); 323 $this->assertCount( 3, $response->get_data() ); 324 // 'offset' works with 'per_page' 325 $request->set_param( 'per_page', 2 ); 326 $response = $this->server->dispatch( $request ); 327 $this->assertCount( 2, $response->get_data() ); 328 // 'offset' takes priority over 'page' 329 $request->set_param( 'page', 3 ); 330 $response = $this->server->dispatch( $request ); 331 $this->assertCount( 2, $response->get_data() ); 332 } 333 334 public function test_get_items_order_query() { 335 wp_set_current_user( $this->admin_id ); 336 $args = array( 337 'comment_approved' => 1, 338 'comment_post_ID' => $this->post_id, 339 ); 340 $this->factory->comment->create( $args ); 341 $this->factory->comment->create( $args ); 342 $id3 = $this->factory->comment->create( $args ); 343 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 344 // order defaults to 'desc' 345 $response = $this->server->dispatch( $request ); 346 $data = $response->get_data(); 347 $this->assertEquals( $id3, $data[0]['id'] ); 348 // order=>asc 349 $request->set_param( 'order', 'asc' ); 350 $response = $this->server->dispatch( $request ); 351 $data = $response->get_data(); 352 $this->assertEquals( $this->approved_id, $data[0]['id'] ); 353 } 354 355 public function test_get_items_private_post_no_permissions() { 356 wp_set_current_user( 0 ); 357 $post_id = $this->factory->post->create( array( 'post_status' => 'private' ) ); 358 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 359 $request->set_param( 'post', $post_id ); 360 $response = $this->server->dispatch( $request ); 361 $this->assertErrorResponse( 'rest_cannot_read_post', $response, 401 ); 362 } 363 364 public function test_get_items_author_arg() { 365 // Authorized 366 wp_set_current_user( $this->admin_id ); 367 $args = array( 368 'comment_approved' => 1, 369 'comment_post_ID' => $this->post_id, 370 'user_id' => $this->author_id, 371 ); 372 $this->factory->comment->create( $args ); 373 $args['user_id'] = $this->subscriber_id; 374 $this->factory->comment->create( $args ); 375 unset( $args['user_id'] ); 376 $this->factory->comment->create( $args ); 377 378 // 'author' limits result to 1 of 3 379 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 380 $request->set_param( 'author', $this->author_id ); 381 $response = $this->server->dispatch( $request ); 382 $this->assertEquals( 200, $response->get_status() ); 383 $comments = $response->get_data(); 384 $this->assertCount( 1, $comments ); 385 // Multiple authors are supported 386 $request->set_param( 'author', array( $this->author_id, $this->subscriber_id ) ); 387 $response = $this->server->dispatch( $request ); 388 $this->assertEquals( 200, $response->get_status() ); 389 $comments = $response->get_data(); 390 $this->assertCount( 2, $comments ); 391 // Unavailable to unauthenticated; defaults to error 392 wp_set_current_user( 0 ); 393 $response = $this->server->dispatch( $request ); 394 $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); 395 } 396 397 public function test_get_items_author_exclude_arg() { 398 // Authorized 399 wp_set_current_user( $this->admin_id ); 400 $args = array( 401 'comment_approved' => 1, 402 'comment_post_ID' => $this->post_id, 403 'user_id' => $this->author_id, 404 ); 405 $this->factory->comment->create( $args ); 406 $args['user_id'] = $this->subscriber_id; 407 $this->factory->comment->create( $args ); 408 unset( $args['user_id'] ); 409 $this->factory->comment->create( $args ); 410 411 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 412 $response = $this->server->dispatch( $request ); 413 $comments = $response->get_data(); 414 $this->assertCount( 4, $comments ); 415 416 // 'author_exclude' limits result to 3 of 4 417 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 418 $request->set_param( 'author_exclude', $this->author_id ); 419 $response = $this->server->dispatch( $request ); 420 $this->assertEquals( 200, $response->get_status() ); 421 $comments = $response->get_data(); 422 $this->assertCount( 3, $comments ); 423 // 'author_exclude' for both comment authors (2 of 4) 424 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 425 $request->set_param( 'author_exclude', array( $this->author_id, $this->subscriber_id ) ); 426 $response = $this->server->dispatch( $request ); 427 $this->assertEquals( 200, $response->get_status() ); 428 $comments = $response->get_data(); 429 $this->assertCount( 2, $comments ); 430 // Unavailable to unauthenticated; defaults to error 431 wp_set_current_user( 0 ); 432 $response = $this->server->dispatch( $request ); 433 $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); 434 } 435 436 public function test_get_items_parent_arg() { 437 $args = array( 438 'comment_approved' => 1, 439 'comment_post_ID' => $this->post_id, 440 ); 441 $parent_id = $this->factory->comment->create( $args ); 442 $parent_id2 = $this->factory->comment->create( $args ); 443 $args['comment_parent'] = $parent_id; 444 $this->factory->comment->create( $args ); 445 $args['comment_parent'] = $parent_id2; 446 $this->factory->comment->create( $args ); 447 // All comments in the database 448 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 449 $response = $this->server->dispatch( $request ); 450 $this->assertCount( 5, $response->get_data() ); 451 // Limit to the parent 452 $request->set_param( 'parent', $parent_id ); 453 $response = $this->server->dispatch( $request ); 454 $this->assertCount( 1, $response->get_data() ); 455 // Limit to two parents 456 $request->set_param( 'parent', array( $parent_id, $parent_id2 ) ); 457 $response = $this->server->dispatch( $request ); 458 $this->assertCount( 2, $response->get_data() ); 459 } 460 461 public function test_get_items_parent_exclude_arg() { 462 $args = array( 463 'comment_approved' => 1, 464 'comment_post_ID' => $this->post_id, 465 ); 466 $parent_id = $this->factory->comment->create( $args ); 467 $parent_id2 = $this->factory->comment->create( $args ); 468 $args['comment_parent'] = $parent_id; 469 $this->factory->comment->create( $args ); 470 $args['comment_parent'] = $parent_id2; 471 $this->factory->comment->create( $args ); 472 // All comments in the database 473 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 474 $response = $this->server->dispatch( $request ); 475 $this->assertCount( 5, $response->get_data() ); 476 // Exclude this particular parent 477 $request->set_param( 'parent_exclude', $parent_id ); 478 $response = $this->server->dispatch( $request ); 479 $this->assertCount( 4, $response->get_data() ); 480 // Exclude both comment parents 481 $request->set_param( 'parent_exclude', array( $parent_id, $parent_id2 ) ); 482 $response = $this->server->dispatch( $request ); 483 $this->assertCount( 3, $response->get_data() ); 484 } 485 486 public function test_get_items_search_query() { 487 wp_set_current_user( $this->admin_id ); 488 $args = array( 489 'comment_approved' => 1, 490 'comment_post_ID' => $this->post_id, 491 'comment_content' => 'foo', 492 'comment_author' => 'Homer J Simpson', 493 ); 494 $id1 = $this->factory->comment->create( $args ); 495 $args['comment_content'] = 'bar'; 496 $this->factory->comment->create( $args ); 497 $args['comment_content'] = 'burrito'; 498 $this->factory->comment->create( $args ); 499 // 3 comments, plus 1 created in construct 500 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 501 $response = $this->server->dispatch( $request ); 502 $this->assertCount( 4, $response->get_data() ); 503 // One matching comments 504 $request->set_param( 'search', 'foo' ); 505 $response = $this->server->dispatch( $request ); 506 $data = $response->get_data(); 507 $this->assertCount( 1, $data ); 508 $this->assertEquals( $id1, $data[0]['id'] ); 509 } 510 511 public function test_get_comments_pagination_headers() { 512 wp_set_current_user( $this->admin_id ); 513 // Start of the index 514 for ( $i = 0; $i < 49; $i++ ) { 515 $this->factory->comment->create( array( 516 'comment_content' => "Comment {$i}", 517 'comment_post_ID' => $this->post_id, 518 ) ); 519 } 520 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 521 $response = $this->server->dispatch( $request ); 522 $headers = $response->get_headers(); 523 $this->assertEquals( 50, $headers['X-WP-Total'] ); 524 $this->assertEquals( 5, $headers['X-WP-TotalPages'] ); 525 $next_link = add_query_arg( array( 526 'page' => 2, 527 ), rest_url( '/wp/v2/comments' ) ); 528 $this->assertFalse( stripos( $headers['Link'], 'rel="prev"' ) ); 529 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 530 // 3rd page 531 $this->factory->comment->create( array( 532 'comment_content' => 'Comment 51', 533 'comment_post_ID' => $this->post_id, 534 ) ); 535 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 536 $request->set_param( 'page', 3 ); 537 $response = $this->server->dispatch( $request ); 538 $headers = $response->get_headers(); 539 $this->assertEquals( 51, $headers['X-WP-Total'] ); 540 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 541 $prev_link = add_query_arg( array( 542 'page' => 2, 543 ), rest_url( '/wp/v2/comments' ) ); 544 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 545 $next_link = add_query_arg( array( 546 'page' => 4, 547 ), rest_url( '/wp/v2/comments' ) ); 548 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 549 // Last page 550 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 551 $request->set_param( 'page', 6 ); 552 $response = $this->server->dispatch( $request ); 553 $headers = $response->get_headers(); 554 $this->assertEquals( 51, $headers['X-WP-Total'] ); 555 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 556 $prev_link = add_query_arg( array( 557 'page' => 5, 558 ), rest_url( '/wp/v2/comments' ) ); 559 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 560 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 561 // Out of bounds 562 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 563 $request->set_param( 'page', 8 ); 564 $response = $this->server->dispatch( $request ); 565 $headers = $response->get_headers(); 566 $this->assertEquals( 51, $headers['X-WP-Total'] ); 567 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 568 $prev_link = add_query_arg( array( 569 'page' => 6, 570 ), rest_url( '/wp/v2/comments' ) ); 571 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 572 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 573 } 574 575 public function test_get_comments_invalid_date() { 576 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 577 $request->set_param( 'after', rand_str() ); 578 $request->set_param( 'before', rand_str() ); 579 $response = $this->server->dispatch( $request ); 580 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 581 } 582 583 public function test_get_comments_valid_date() { 584 $comment1 = $this->factory->comment->create( array( 585 'comment_date' => '2016-01-15T00:00:00Z', 586 'comment_post_ID' => $this->post_id, 587 ) ); 588 $comment2 = $this->factory->comment->create( array( 589 'comment_date' => '2016-01-16T00:00:00Z', 590 'comment_post_ID' => $this->post_id, 591 ) ); 592 $comment3 = $this->factory->comment->create( array( 593 'comment_date' => '2016-01-17T00:00:00Z', 594 'comment_post_ID' => $this->post_id, 595 ) ); 596 597 $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 598 $request->set_param( 'after', '2016-01-15T00:00:00Z' ); 599 $request->set_param( 'before', '2016-01-17T00:00:00Z' ); 600 $response = $this->server->dispatch( $request ); 601 $data = $response->get_data(); 602 $this->assertCount( 1, $data ); 603 $this->assertEquals( $comment2, $data[0]['id'] ); 604 } 605 606 public function test_get_item() { 607 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 608 609 $response = $this->server->dispatch( $request ); 610 $this->assertEquals( 200, $response->get_status() ); 611 612 $data = $response->get_data(); 613 $this->check_comment_data( $data, 'view', $response->get_links() ); 614 } 615 616 public function test_prepare_item() { 617 wp_set_current_user( $this->admin_id ); 618 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 619 $request->set_query_params( array( 620 'context' => 'edit', 621 ) ); 622 623 $response = $this->server->dispatch( $request ); 624 $this->assertEquals( 200, $response->get_status() ); 625 626 $data = $response->get_data(); 627 $this->check_comment_data( $data, 'edit', $response->get_links() ); 628 } 629 630 public function test_get_comment_author_avatar_urls() { 631 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 632 633 $response = $this->server->dispatch( $request ); 634 635 $data = $response->get_data(); 636 $this->assertArrayHasKey( 24, $data['author_avatar_urls'] ); 637 $this->assertArrayHasKey( 48, $data['author_avatar_urls'] ); 638 $this->assertArrayHasKey( 96, $data['author_avatar_urls'] ); 639 640 $comment = get_comment( $this->approved_id ); 641 /** 642 * Ignore the subdomain, since 'get_avatar_url randomly sets the Gravatar 643 * server when building the url string. 644 */ 645 $this->assertEquals( substr( get_avatar_url( $comment->comment_author_email ), 9 ), substr( $data['author_avatar_urls'][96], 9 ) ); 646 } 647 648 public function test_get_comment_invalid_id() { 649 $request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 650 651 $response = $this->server->dispatch( $request ); 652 $this->assertErrorResponse( 'rest_comment_invalid_id', $response, 404 ); 653 } 654 655 public function test_get_comment_invalid_context() { 656 wp_set_current_user( 0 ); 657 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $this->approved_id ) ); 658 $request->set_param( 'context', 'edit' ); 659 $response = $this->server->dispatch( $request ); 660 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 661 } 662 663 public function test_get_comment_invalid_post_id() { 664 wp_set_current_user( 0 ); 665 $comment_id = $this->factory->comment->create( array( 666 'comment_approved' => 1, 667 'comment_post_ID' => REST_TESTS_IMPOSSIBLY_HIGH_NUMBER, 668 )); 669 $request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . $comment_id ); 670 671 $response = $this->server->dispatch( $request ); 672 $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); 673 } 674 675 public function test_get_comment_invalid_post_id_as_admin() { 676 wp_set_current_user( $this->admin_id ); 677 $comment_id = $this->factory->comment->create( array( 678 'comment_approved' => 1, 679 'comment_post_ID' => REST_TESTS_IMPOSSIBLY_HIGH_NUMBER, 680 )); 681 $request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . $comment_id ); 682 683 $response = $this->server->dispatch( $request ); 684 $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); 685 } 686 687 public function test_get_comment_not_approved() { 688 wp_set_current_user( 0 ); 689 690 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $this->hold_id ) ); 691 692 $response = $this->server->dispatch( $request ); 693 $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); 694 } 695 696 public function test_get_comment_not_approved_same_user() { 697 wp_set_current_user( $this->subscriber_id ); 698 699 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $this->hold_id ) ); 700 701 $response = $this->server->dispatch( $request ); 702 $this->assertEquals( 200, $response->get_status() ); 703 } 704 705 public function test_get_comment_with_children_link() { 706 $comment_id_1 = $this->factory->comment->create( array( 707 'comment_approved' => 1, 708 'comment_post_ID' => $this->post_id, 709 'user_id' => $this->subscriber_id, 710 ) ); 711 712 $child_comment = $this->factory->comment->create( array( 713 'comment_approved' => 1, 714 'comment_parent' => $comment_id_1, 715 'comment_post_ID' => $this->post_id, 716 'user_id' => $this->subscriber_id, 717 ) ); 718 719 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $comment_id_1 ) ); 720 $response = $this->server->dispatch( $request ); 721 $this->assertEquals( 200, $response->get_status() ); 722 $this->assertArrayHasKey( 'children', $response->get_links() ); 723 } 724 725 public function test_get_comment_without_children_link() { 726 $comment_id_1 = $this->factory->comment->create( array( 727 'comment_approved' => 1, 728 'comment_post_ID' => $this->post_id, 729 'user_id' => $this->subscriber_id, 730 ) ); 731 732 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $comment_id_1 ) ); 733 $response = $this->server->dispatch( $request ); 734 $this->assertEquals( 200, $response->get_status() ); 735 $this->assertArrayNotHasKey( 'children', $response->get_links() ); 736 } 737 738 public function test_create_item() { 739 wp_set_current_user( 0 ); 740 741 $params = array( 742 'post' => $this->post_id, 743 'author_name' => 'Comic Book Guy', 744 'author_email' => 'cbg@androidsdungeon.com', 745 'author_url' => 'http://androidsdungeon.com', 746 'content' => 'Worst Comment Ever!', 747 'date' => '2014-11-07T10:14:25', 748 ); 749 750 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 751 $request->add_header( 'content-type', 'application/json' ); 752 $request->set_body( wp_json_encode( $params ) ); 753 754 $response = $this->server->dispatch( $request ); 755 $this->assertEquals( 201, $response->get_status() ); 756 757 $data = $response->get_data(); 758 $this->check_comment_data( $data, 'view', $response->get_links() ); 759 $this->assertEquals( 'hold', $data['status'] ); 760 $this->assertEquals( '2014-11-07T10:14:25', $data['date'] ); 761 $this->assertEquals( $this->post_id, $data['post'] ); 762 } 763 764 public function test_create_item_using_accepted_content_raw_value() { 765 wp_set_current_user( 0 ); 766 767 $params = array( 768 'post' => $this->post_id, 769 'author_name' => 'Reverend Lovejoy', 770 'author_email' => 'lovejoy@example.com', 771 'author_url' => 'http://timothylovejoy.jr', 772 'content' => array( 773 'raw' => 'Once something has been approved by the government, it\'s no longer immoral.', 774 ), 775 ); 776 777 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 778 $request->add_header( 'content-type', 'application/json' ); 779 $request->set_body( wp_json_encode( $params ) ); 780 781 $response = $this->server->dispatch( $request ); 782 $this->assertEquals( 201, $response->get_status() ); 783 784 $data = $response->get_data(); 785 $new_comment = get_comment( $data['id'] ); 786 $this->assertEquals( $params['content']['raw'], $new_comment->comment_content ); 787 } 788 789 public function test_create_comment_missing_required_author_name_and_email_per_option_value() { 790 update_option( 'require_name_email', 1 ); 791 792 $params = array( 793 'post' => $this->post_id, 794 'content' => 'Now, I don\'t want you to worry class. These tests will have no affect on your grades. They merely determine your future social status and financial success. If any.', 795 ); 796 797 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 798 $request->add_header( 'content-type', 'application/json' ); 799 $request->set_body( wp_json_encode( $params ) ); 800 801 $response = $this->server->dispatch( $request ); 802 $this->assertErrorResponse( 'rest_comment_author_data_required', $response, 400 ); 803 804 update_option( 'require_name_email', 0 ); 805 } 806 807 public function test_create_comment_missing_required_author_name_per_option_value() { 808 update_option( 'require_name_email', 1 ); 809 810 $params = array( 811 'post' => $this->post_id, 812 'author_email' => 'ekrabappel@springfield-elementary.edu', 813 'content' => 'Now, I don\'t want you to worry class. These tests will have no affect on your grades. They merely determine your future social status and financial success. If any.', 814 ); 815 816 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 817 $request->add_header( 'content-type', 'application/json' ); 818 $request->set_body( wp_json_encode( $params ) ); 819 820 $response = $this->server->dispatch( $request ); 821 $this->assertErrorResponse( 'rest_comment_author_required', $response, 400 ); 822 823 update_option( 'require_name_email', 0 ); 824 } 825 826 public function test_create_comment_missing_required_author_email_per_option_value() { 827 update_option( 'require_name_email', 1 ); 828 829 $params = array( 830 'post' => $this->post_id, 831 'author_name' => 'Edna Krabappel', 832 'content' => 'Now, I don\'t want you to worry class. These tests will have no affect on your grades. They merely determine your future social status and financial success. If any.', 833 ); 834 835 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 836 $request->add_header( 'content-type', 'application/json' ); 837 $request->set_body( wp_json_encode( $params ) ); 838 839 $response = $this->server->dispatch( $request ); 840 $this->assertErrorResponse( 'rest_comment_author_email_required', $response, 400 ); 841 842 update_option( 'require_name_email', 0 ); 843 } 844 845 public function test_create_item_invalid_blank_content() { 846 wp_set_current_user( 0 ); 847 848 $params = array( 849 'post' => $this->post_id, 850 'author_name' => 'Reverend Lovejoy', 851 'author_email' => 'lovejoy@example.com', 852 'author_url' => 'http://timothylovejoy.jr', 853 'content' => '', 854 ); 855 856 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 857 $request->add_header( 'content-type', 'application/json' ); 858 $request->set_body( wp_json_encode( $params ) ); 859 860 $response = $this->server->dispatch( $request ); 861 $this->assertErrorResponse( 'rest_comment_content_invalid', $response, 400 ); 862 } 863 864 public function test_create_item_invalid_date() { 865 wp_set_current_user( 0 ); 866 867 $params = array( 868 'post' => $this->post_id, 869 'author_name' => 'Reverend Lovejoy', 870 'author_email' => 'lovejoy@example.com', 871 'author_url' => 'http://timothylovejoy.jr', 872 'content' => 'It\'s all over\, people! We don\'t have a prayer!', 873 'date' => rand_str(), 874 ); 875 876 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 877 $request->add_header( 'content-type', 'application/json' ); 878 $request->set_body( wp_json_encode( $params ) ); 879 880 $response = $this->server->dispatch( $request ); 881 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 882 } 883 884 885 public function test_create_item_assign_different_user() { 886 $subscriber_id = $this->factory->user->create( array( 887 'role' => 'subscriber', 888 'user_email' => 'cbg@androidsdungeon.com', 889 )); 890 891 wp_set_current_user( $this->admin_id ); 892 $params = array( 893 'post' => $this->post_id, 894 'author_name' => 'Comic Book Guy', 895 'author_email' => 'cbg@androidsdungeon.com', 896 'author_url' => 'http://androidsdungeon.com', 897 'author' => $subscriber_id, 898 'content' => 'Worst Comment Ever!', 899 'date' => '2014-11-07T10:14:25', 900 ); 901 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 902 $request->add_header( 'content-type', 'application/json' ); 903 $request->set_body( wp_json_encode( $params ) ); 904 $response = $this->server->dispatch( $request ); 905 $this->assertEquals( 201, $response->get_status() ); 906 907 $data = $response->get_data(); 908 $this->assertEquals( $subscriber_id, $data['author'] ); 909 $this->assertEquals( '127.0.0.1', $data['author_ip'] ); 910 } 911 912 public function test_create_comment_without_type() { 913 $post_id = $this->factory->post->create(); 914 wp_set_current_user( $this->admin_id ); 915 916 $params = array( 917 'post' => $post_id, 918 'author' => $this->admin_id, 919 'author_name' => 'Comic Book Guy', 920 'author_email' => 'cbg@androidsdungeon.com', 921 'author_url' => 'http://androidsdungeon.com', 922 'content' => 'Worst Comment Ever!', 923 'date' => '2014-11-07T10:14:25', 924 ); 925 926 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 927 $request->add_header( 'content-type', 'application/json' ); 928 $request->set_body( wp_json_encode( $params ) ); 929 930 $response = $this->server->dispatch( $request ); 931 $this->assertEquals( 201, $response->get_status() ); 932 933 $data = $response->get_data(); 934 $this->assertEquals( 'comment', $data['type'] ); 935 936 $comment_id = $data['id']; 937 938 // Make sure the new comment is present in the collection. 939 $collection = new WP_REST_Request( 'GET', '/wp/v2/comments' ); 940 $collection->set_param( 'post', $post_id ); 941 $collection_response = $this->server->dispatch( $collection ); 942 $collection_data = $collection_response->get_data(); 943 $this->assertEquals( $comment_id, $collection_data[0]['id'] ); 944 } 945 946 public function test_create_item_current_user() { 947 $user_id = $this->factory->user->create( array( 948 'role' => 'subscriber', 949 'user_email' => 'lylelanley@example.com', 950 'first_name' => 'Lyle', 951 'last_name' => 'Lanley', 952 'display_name' => 'Lyle Lanley', 953 'user_url' => 'http://simpsons.wikia.com/wiki/Lyle_Lanley', 954 )); 955 956 wp_set_current_user( $user_id ); 957 958 $params = array( 959 'post' => $this->post_id, 960 'content' => "Well sir, there's nothing on earth like a genuine, bona fide, electrified, six-car Monorail!", 961 ); 962 963 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 964 $request->add_header( 'content-type', 'application/json' ); 965 $request->set_body( wp_json_encode( $params ) ); 966 $response = $this->server->dispatch( $request ); 967 968 $this->assertEquals( 201, $response->get_status() ); 969 $data = $response->get_data(); 970 $this->assertEquals( $user_id, $data['author'] ); 971 972 // Check author data matches 973 $author = get_user_by( 'id', $user_id ); 974 $comment = get_comment( $data['id'] ); 975 $this->assertEquals( $author->display_name, $comment->comment_author ); 976 $this->assertEquals( $author->user_email, $comment->comment_author_email ); 977 $this->assertEquals( $author->user_url, $comment->comment_author_url ); 978 } 979 980 public function test_create_comment_other_user() { 981 wp_set_current_user( $this->admin_id ); 982 983 $params = array( 984 'post' => $this->post_id, 985 'author_name' => 'Homer Jay Simpson', 986 'author_email' => 'chunkylover53@aol.com', 987 'author_url' => 'http://compuglobalhypermeganet.com', 988 'content' => 'Here\’s to alcohol: the cause of, and solution to, all of life\’s problems.', 989 'author' => $this->subscriber_id, 990 ); 991 992 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 993 $request->add_header( 'content-type', 'application/json' ); 994 $request->set_body( wp_json_encode( $params ) ); 995 $response = $this->server->dispatch( $request ); 996 997 $this->assertEquals( 201, $response->get_status() ); 998 $data = $response->get_data(); 999 $this->assertEquals( $this->subscriber_id, $data['author'] ); 1000 $this->assertEquals( 'Homer Jay Simpson', $data['author_name'] ); 1001 $this->assertEquals( 'chunkylover53@aol.com', $data['author_email'] ); 1002 $this->assertEquals( 'http://compuglobalhypermeganet.com', $data['author_url'] ); 1003 } 1004 1005 public function test_create_comment_other_user_without_permission() { 1006 wp_set_current_user( $this->subscriber_id ); 1007 1008 $params = array( 1009 'post' => $this->post_id, 1010 'author_name' => 'Homer Jay Simpson', 1011 'author_email' => 'chunkylover53@aol.com', 1012 'author_url' => 'http://compuglobalhypermeganet.com', 1013 'content' => 'Here\’s to alcohol: the cause of, and solution to, all of life\’s problems.', 1014 'author' => $this->admin_id, 1015 ); 1016 1017 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1018 $request->add_header( 'content-type', 'application/json' ); 1019 $request->set_body( wp_json_encode( $params ) ); 1020 $response = $this->server->dispatch( $request ); 1021 1022 $this->assertErrorResponse( 'rest_comment_invalid_author', $response, 403 ); 1023 } 1024 1025 public function test_create_comment_karma_without_permission() { 1026 wp_set_current_user( $this->subscriber_id ); 1027 1028 $params = array( 1029 'post' => $this->post_id, 1030 'author_name' => 'Homer Jay Simpson', 1031 'author_email' => 'chunkylover53@aol.com', 1032 'author_url' => 'http://compuglobalhypermeganet.com', 1033 'content' => 'Here\’s to alcohol: the cause of, and solution to, all of life\’s problems.', 1034 'author' => $this->subscriber_id, 1035 'karma' => 100, 1036 ); 1037 1038 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1039 $request->add_header( 'content-type', 'application/json' ); 1040 $request->set_body( wp_json_encode( $params ) ); 1041 $response = $this->server->dispatch( $request ); 1042 1043 $this->assertErrorResponse( 'rest_comment_invalid_karma', $response, 403 ); 1044 } 1045 1046 public function test_create_comment_status_without_permission() { 1047 wp_set_current_user( $this->subscriber_id ); 1048 1049 $params = array( 1050 'post' => $this->post_id, 1051 'author_name' => 'Homer Jay Simpson', 1052 'author_email' => 'chunkylover53@aol.com', 1053 'author_url' => 'http://compuglobalhypermeganet.com', 1054 'content' => 'Here\’s to alcohol: the cause of, and solution to, all of life\’s problems.', 1055 'author' => $this->subscriber_id, 1056 'status' => 'approved', 1057 ); 1058 1059 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1060 $request->add_header( 'content-type', 'application/json' ); 1061 $request->set_body( wp_json_encode( $params ) ); 1062 $response = $this->server->dispatch( $request ); 1063 1064 $this->assertErrorResponse( 'rest_comment_invalid_status', $response, 403 ); 1065 } 1066 1067 public function test_create_comment_with_status_and_IP() { 1068 $post_id = $this->factory->post->create(); 1069 wp_set_current_user( $this->admin_id ); 1070 1071 $params = array( 1072 'post' => $post_id, 1073 'author_name' => 'Comic Book Guy', 1074 'author_email' => 'cbg@androidsdungeon.com', 1075 'author_ip' => '139.130.4.5', 1076 'author_url' => 'http://androidsdungeon.com', 1077 'content' => 'Worst Comment Ever!', 1078 'status' => 'approved', 1079 ); 1080 1081 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1082 $request->add_header( 'content-type', 'application/json' ); 1083 $request->set_body( wp_json_encode( $params ) ); 1084 1085 $response = $this->server->dispatch( $request ); 1086 $this->assertEquals( 201, $response->get_status() ); 1087 1088 $data = $response->get_data(); 1089 $this->assertEquals( 'approved', $data['status'] ); 1090 $this->assertEquals( '139.130.4.5', $data['author_ip'] ); 1091 } 1092 1093 public function test_create_comment_invalid_author_IP() { 1094 wp_set_current_user( $this->admin_id ); 1095 1096 $params = array( 1097 'author_name' => 'Comic Book Guy', 1098 'author_email' => 'cbg@androidsdungeon.com', 1099 'author_url' => 'http://androidsdungeon.com', 1100 'author_ip' => '867.5309', 1101 'content' => 'Worst Comment Ever!', 1102 'status' => 'approved', 1103 ); 1104 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1105 $request->add_header( 'content-type', 'application/json' ); 1106 $request->set_body( wp_json_encode( $params ) ); 1107 1108 $response = $this->server->dispatch( $request ); 1109 1110 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1111 } 1112 1113 public function test_create_comment_no_post_id() { 1114 wp_set_current_user( $this->admin_id ); 1115 1116 $params = array( 1117 'author_name' => 'Comic Book Guy', 1118 'author_email' => 'cbg@androidsdungeon.com', 1119 'author_url' => 'http://androidsdungeon.com', 1120 'content' => 'Worst Comment Ever!', 1121 'status' => 'approved', 1122 ); 1123 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1124 $request->add_header( 'content-type', 'application/json' ); 1125 $request->set_body( wp_json_encode( $params ) ); 1126 1127 $response = $this->server->dispatch( $request ); 1128 $this->assertEquals( 201, $response->get_status() ); 1129 } 1130 1131 public function test_create_comment_no_post_id_no_permission() { 1132 wp_set_current_user( $this->subscriber_id ); 1133 1134 $params = array( 1135 'author_name' => 'Homer Jay Simpson', 1136 'author_email' => 'chunkylover53@aol.com', 1137 'author_url' => 'http://compuglobalhypermeganet.com', 1138 'content' => 'Here\’s to alcohol: the cause of, and solution to, all of life\’s problems.', 1139 'author' => $this->subscriber_id, 1140 ); 1141 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1142 $request->add_header( 'content-type', 'application/json' ); 1143 $request->set_body( wp_json_encode( $params ) ); 1144 1145 $response = $this->server->dispatch( $request ); 1146 1147 $this->assertErrorResponse( 'rest_comment_invalid_post_id', $response, 403 ); 1148 } 1149 1150 public function test_create_comment_draft_post() { 1151 wp_set_current_user( $this->subscriber_id ); 1152 1153 $params = array( 1154 'post' => $this->draft_id, 1155 'author_name' => 'Ishmael', 1156 'author_email' => 'herman-melville@earthlink.net', 1157 'author_url' => 'https://en.wikipedia.org/wiki/Herman_Melville', 1158 'content' => 'Call me Ishmael.', 1159 'author' => $this->subscriber_id, 1160 ); 1161 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1162 $request->add_header( 'content-type', 'application/json' ); 1163 $request->set_body( wp_json_encode( $params ) ); 1164 1165 $response = $this->server->dispatch( $request ); 1166 1167 $this->assertErrorResponse( 'rest_comment_draft_post', $response, 403 ); 1168 } 1169 1170 public function test_create_comment_trash_post() { 1171 wp_set_current_user( $this->subscriber_id ); 1172 1173 $params = array( 1174 'post' => $this->trash_id, 1175 'author_name' => 'Ishmael', 1176 'author_email' => 'herman-melville@earthlink.net', 1177 'author_url' => 'https://en.wikipedia.org/wiki/Herman_Melville', 1178 'content' => 'Call me Ishmael.', 1179 'author' => $this->subscriber_id, 1180 ); 1181 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1182 $request->add_header( 'content-type', 'application/json' ); 1183 $request->set_body( wp_json_encode( $params ) ); 1184 1185 $response = $this->server->dispatch( $request ); 1186 1187 $this->assertErrorResponse( 'rest_comment_trash_post', $response, 403 ); 1188 } 1189 1190 public function test_create_comment_private_post_invalid_permission() { 1191 wp_set_current_user( $this->subscriber_id ); 1192 1193 $params = array( 1194 'post' => $this->private_id, 1195 'author_name' => 'Homer Jay Simpson', 1196 'author_email' => 'chunkylover53@aol.com', 1197 'author_url' => 'http://compuglobalhypermeganet.com', 1198 'content' => 'I\’d be a vegetarian if bacon grew on trees.', 1199 'author' => $this->subscriber_id, 1200 ); 1201 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1202 $request->add_header( 'content-type', 'application/json' ); 1203 $request->set_body( wp_json_encode( $params ) ); 1204 1205 $response = $this->server->dispatch( $request ); 1206 1207 $this->assertErrorResponse( 'rest_cannot_read_post', $response, 403 ); 1208 } 1209 1210 public function test_create_item_duplicate() { 1211 global $wp_version; 1212 if ( version_compare( $wp_version, '4.7-alpha', '<' ) ) { 1213 return $this->markTestSkipped( 'WordPress version not supported.' ); 1214 } 1215 1216 $this->factory->comment->create( 1217 array( 1218 'comment_post_ID' => $this->post_id, 1219 'comment_author' => 'Guy N. Cognito', 1220 'comment_author_email' => 'chunkylover53@aol.co.uk', 1221 'comment_content' => 'Homer? Who is Homer? My name is Guy N. Cognito.', 1222 ) 1223 ); 1224 wp_set_current_user( 0 ); 1225 1226 $params = array( 1227 'post' => $this->post_id, 1228 'author_name' => 'Guy N. Cognito', 1229 'author_email' => 'chunkylover53@aol.co.uk', 1230 'content' => 'Homer? Who is Homer? My name is Guy N. Cognito.', 1231 ); 1232 1233 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1234 $request->add_header( 'content-type', 'application/json' ); 1235 $request->set_body( wp_json_encode( $params ) ); 1236 $response = $this->server->dispatch( $request ); 1237 1238 $this->assertEquals( 409, $response->get_status() ); 1239 } 1240 1241 public function test_create_comment_closed() { 1242 $post_id = $this->factory->post->create( array( 1243 'comment_status' => 'closed', 1244 )); 1245 wp_set_current_user( 0 ); 1246 1247 $params = array( 1248 'post' => $post_id, 1249 ); 1250 1251 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1252 $request->add_header( 'content-type', 'application/json' ); 1253 $request->set_body( wp_json_encode( $params ) ); 1254 $response = $this->server->dispatch( $request ); 1255 1256 $this->assertEquals( 403, $response->get_status() ); 1257 } 1258 1259 public function test_create_comment_require_login() { 1260 wp_set_current_user( 0 ); 1261 update_option( 'comment_registration', 1 ); 1262 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1263 $request->set_param( 'post', $this->post_id ); 1264 $response = $this->server->dispatch( $request ); 1265 $this->assertEquals( 401, $response->get_status() ); 1266 $data = $response->get_data(); 1267 $this->assertEquals( 'rest_comment_login_required', $data['code'] ); 1268 } 1269 1270 public function test_create_item_invalid_author() { 1271 wp_set_current_user( $this->admin_id ); 1272 1273 $params = array( 1274 'post' => $this->post_id, 1275 'author' => REST_TESTS_IMPOSSIBLY_HIGH_NUMBER, 1276 'content' => 'It\'s all over\, people! We don\'t have a prayer!', 1277 ); 1278 1279 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1280 $request->add_header( 'content-type', 'application/json' ); 1281 $request->set_body( wp_json_encode( $params ) ); 1282 1283 $response = $this->server->dispatch( $request ); 1284 $this->assertErrorResponse( 'rest_comment_author_invalid', $response, 400 ); 1285 } 1286 1287 public function test_create_item_pull_author_info() { 1288 wp_set_current_user( $this->admin_id ); 1289 1290 $author = new WP_User( $this->author_id ); 1291 $params = array( 1292 'post' => $this->post_id, 1293 'author' => $this->author_id, 1294 'content' => 'It\'s all over\, people! We don\'t have a prayer!', 1295 ); 1296 1297 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1298 $request->add_header( 'content-type', 'application/json' ); 1299 $request->set_body( wp_json_encode( $params ) ); 1300 1301 $response = $this->server->dispatch( $request ); 1302 1303 $result = $response->get_data(); 1304 $this->assertSame( $this->author_id, $result['author'] ); 1305 $this->assertSame( 'Sea Captain', $result['author_name'] ); 1306 $this->assertSame( 'captain@thefryingdutchman.com', $result['author_email'] ); 1307 $this->assertSame( 'http://thefryingdutchman.com', $result['author_url'] ); 1308 } 1309 1310 public function test_create_comment_two_times() { 1311 global $wp_version; 1312 if ( version_compare( $wp_version, '4.7-alpha', '<' ) ) { 1313 return $this->markTestSkipped( 'WordPress version not supported.' ); 1314 } 1315 1316 wp_set_current_user( 0 ); 1317 1318 $params = array( 1319 'post' => $this->post_id, 1320 'author_name' => 'Comic Book Guy', 1321 'author_email' => 'cbg@androidsdungeon.com', 1322 'author_url' => 'http://androidsdungeon.com', 1323 'content' => 'Worst Comment Ever!', 1324 ); 1325 1326 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1327 $request->add_header( 'content-type', 'application/json' ); 1328 $request->set_body( wp_json_encode( $params ) ); 1329 1330 $response = $this->server->dispatch( $request ); 1331 $this->assertEquals( 201, $response->get_status() ); 1332 1333 $params = array( 1334 'post' => $this->post_id, 1335 'author_name' => 'Comic Book Guy', 1336 'author_email' => 'cbg@androidsdungeon.com', 1337 'author_url' => 'http://androidsdungeon.com', 1338 'content' => 'Shakes fist at sky', 1339 ); 1340 1341 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1342 $request->add_header( 'content-type', 'application/json' ); 1343 $request->set_body( wp_json_encode( $params ) ); 1344 1345 $response = $this->server->dispatch( $request ); 1346 $this->assertEquals( 400, $response->get_status() ); 1347 } 1348 1349 public function test_update_item() { 1350 $post_id = $this->factory->post->create(); 1351 1352 wp_set_current_user( $this->admin_id ); 1353 1354 $params = array( 1355 'author' => $this->subscriber_id, 1356 'author_name' => 'Disco Stu', 1357 'author_url' => 'http://stusdisco.com', 1358 'author_email' => 'stu@stusdisco.com', 1359 'author_ip' => '4.4.4.4', 1360 'content' => 'Testing.', 1361 'date' => '2014-11-07T10:14:25', 1362 'karma' => 100, 1363 'post' => $post_id, 1364 ); 1365 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 1366 $request->add_header( 'content-type', 'application/json' ); 1367 $request->set_body( wp_json_encode( $params ) ); 1368 1369 $response = $this->server->dispatch( $request ); 1370 $this->assertEquals( 200, $response->get_status() ); 1371 1372 $comment = $response->get_data(); 1373 $updated = get_comment( $this->approved_id ); 1374 $this->assertEquals( $params['content'], $comment['content']['raw'] ); 1375 $this->assertEquals( $params['author'], $comment['author'] ); 1376 $this->assertEquals( $params['author_name'], $comment['author_name'] ); 1377 $this->assertEquals( $params['author_url'], $comment['author_url'] ); 1378 $this->assertEquals( $params['author_email'], $comment['author_email'] ); 1379 $this->assertEquals( $params['author_ip'], $comment['author_ip'] ); 1380 $this->assertEquals( $params['post'], $comment['post'] ); 1381 $this->assertEquals( $params['karma'], $comment['karma'] ); 1382 1383 $this->assertEquals( mysql_to_rfc3339( $updated->comment_date ), $comment['date'] ); 1384 $this->assertEquals( '2014-11-07T10:14:25', $comment['date'] ); 1385 } 1386 1387 public function test_update_comment_status() { 1388 wp_set_current_user( $this->admin_id ); 1389 1390 $comment_id = $this->factory->comment->create( array( 1391 'comment_approved' => 0, 1392 'comment_post_ID' => $this->post_id, 1393 )); 1394 1395 $params = array( 1396 'status' => 'approve', 1397 ); 1398 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $comment_id ) ); 1399 $request->add_header( 'content-type', 'application/json' ); 1400 $request->set_body( wp_json_encode( $params ) ); 1401 1402 $response = $this->server->dispatch( $request ); 1403 $this->assertEquals( 200, $response->get_status() ); 1404 1405 $comment = $response->get_data(); 1406 $updated = get_comment( $comment_id ); 1407 $this->assertEquals( 'approved', $comment['status'] ); 1408 $this->assertEquals( 1, $updated->comment_approved ); 1409 } 1410 1411 public function test_update_comment_field_does_not_use_default_values() { 1412 wp_set_current_user( $this->admin_id ); 1413 1414 $comment_id = $this->factory->comment->create( array( 1415 'comment_approved' => 0, 1416 'comment_post_ID' => $this->post_id, 1417 'comment_content' => 'some content', 1418 )); 1419 1420 $params = array( 1421 'status' => 'approve', 1422 ); 1423 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $comment_id ) ); 1424 $request->add_header( 'content-type', 'application/json' ); 1425 $request->set_body( wp_json_encode( $params ) ); 1426 1427 $response = $this->server->dispatch( $request ); 1428 $this->assertEquals( 200, $response->get_status() ); 1429 1430 $comment = $response->get_data(); 1431 $updated = get_comment( $comment_id ); 1432 $this->assertEquals( 'approved', $comment['status'] ); 1433 $this->assertEquals( 1, $updated->comment_approved ); 1434 $this->assertEquals( 'some content', $updated->comment_content ); 1435 } 1436 1437 public function test_update_comment_date_gmt() { 1438 wp_set_current_user( $this->admin_id ); 1439 1440 $params = array( 1441 'date_gmt' => '2015-05-07T10:14:25', 1442 'content' => 'I\'ll be deep in the cold, cold ground before I recognize Missouri.', 1443 ); 1444 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 1445 $request->add_header( 'content-type', 'application/json' ); 1446 $request->set_body( wp_json_encode( $params ) ); 1447 1448 $response = $this->server->dispatch( $request ); 1449 $this->assertEquals( 200, $response->get_status() ); 1450 1451 $comment = $response->get_data(); 1452 $updated = get_comment( $this->approved_id ); 1453 $this->assertEquals( $params['date_gmt'], $comment['date_gmt'] ); 1454 $this->assertEquals( $params['date_gmt'], mysql_to_rfc3339( $updated->comment_date_gmt ) ); 1455 } 1456 1457 public function test_update_comment_invalid_type() { 1458 wp_set_current_user( $this->admin_id ); 1459 1460 $params = array( 1461 'type' => 'trackback', 1462 ); 1463 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 1464 $request->add_header( 'content-type', 'application/json' ); 1465 $request->set_body( wp_json_encode( $params ) ); 1466 1467 $response = $this->server->dispatch( $request ); 1468 $this->assertErrorResponse( 'rest_comment_invalid_type', $response, 404 ); 1469 } 1470 1471 public function test_update_comment_with_raw_property() { 1472 wp_set_current_user( $this->admin_id ); 1473 1474 $params = array( 1475 'content' => array( 1476 'raw' => 'What the heck kind of name is Persephone?', 1477 ), 1478 ); 1479 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 1480 $request->add_header( 'content-type', 'application/json' ); 1481 $request->set_body( wp_json_encode( $params ) ); 1482 1483 $response = $this->server->dispatch( $request ); 1484 1485 $this->assertEquals( 200, $response->get_status() ); 1486 1487 $comment = $response->get_data(); 1488 $updated = get_comment( $this->approved_id ); 1489 $this->assertEquals( $params['content']['raw'], $updated->comment_content ); 1490 } 1491 1492 public function test_update_item_invalid_date() { 1493 wp_set_current_user( $this->admin_id ); 1494 1495 $params = array( 1496 'content' => rand_str(), 1497 'date' => rand_str(), 1498 ); 1499 1500 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 1501 $request->add_header( 'content-type', 'application/json' ); 1502 $request->set_body( wp_json_encode( $params ) ); 1503 1504 $response = $this->server->dispatch( $request ); 1505 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1506 } 1507 1508 public function test_update_item_invalid_date_gmt() { 1509 wp_set_current_user( $this->admin_id ); 1510 1511 $params = array( 1512 'content' => rand_str(), 1513 'date_gmt' => rand_str(), 1514 ); 1515 1516 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 1517 $request->add_header( 'content-type', 'application/json' ); 1518 $request->set_body( wp_json_encode( $params ) ); 1519 1520 $response = $this->server->dispatch( $request ); 1521 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1522 } 1523 1524 public function test_update_comment_invalid_id() { 1525 wp_set_current_user( 0 ); 1526 1527 $params = array( 1528 'content' => 'Oh, they have the internet on computers now!', 1529 ); 1530 $request = new WP_REST_Request( 'PUT', '/wp/v2/comments/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 1531 $request->add_header( 'content-type', 'application/json' ); 1532 $request->set_body( wp_json_encode( $params ) ); 1533 1534 $response = $this->server->dispatch( $request ); 1535 $this->assertErrorResponse( 'rest_comment_invalid_id', $response, 404 ); 1536 } 1537 1538 public function test_update_comment_invalid_permission() { 1539 wp_set_current_user( 0 ); 1540 1541 $params = array( 1542 'content' => 'Disco Stu likes disco music.', 1543 ); 1544 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $this->hold_id ) ); 1545 $request->add_header( 'content-type', 'application/json' ); 1546 $request->set_body( wp_json_encode( $params ) ); 1547 1548 $response = $this->server->dispatch( $request ); 1549 $this->assertErrorResponse( 'rest_cannot_edit', $response, 401 ); 1550 } 1551 1552 public function test_update_comment_private_post_invalid_permission() { 1553 $private_comment_id = $this->factory->comment->create( array( 1554 'comment_approved' => 1, 1555 'comment_post_ID' => $this->private_id, 1556 'user_id' => 0, 1557 )); 1558 1559 wp_set_current_user( $this->subscriber_id ); 1560 1561 $params = array( 1562 'content' => 'Disco Stu likes disco music.', 1563 ); 1564 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%d', $private_comment_id ) ); 1565 $request->add_header( 'content-type', 'application/json' ); 1566 $request->set_body( wp_json_encode( $params ) ); 1567 1568 $response = $this->server->dispatch( $request ); 1569 $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); 1570 } 1571 1572 public function test_update_comment_with_children_link() { 1573 wp_set_current_user( $this->admin_id ); 1574 $comment_id_1 = $this->factory->comment->create( array( 1575 'comment_approved' => 1, 1576 'comment_post_ID' => $this->post_id, 1577 'user_id' => $this->subscriber_id, 1578 ) ); 1579 1580 $child_comment = $this->factory->comment->create( array( 1581 'comment_approved' => 1, 1582 'comment_post_ID' => $this->post_id, 1583 'user_id' => $this->subscriber_id, 1584 ) ); 1585 1586 // Check if comment 1 does not have the child link. 1587 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $comment_id_1 ) ); 1588 $response = $this->server->dispatch( $request ); 1589 $this->assertEquals( 200, $response->get_status() ); 1590 $this->assertArrayNotHasKey( 'children', $response->get_links() ); 1591 1592 // Change the comment parent. 1593 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/comments/%s', $child_comment ) ); 1594 $request->set_param( 'parent', $comment_id_1 ); 1595 $request->set_param( 'content', rand_str() ); 1596 $response = $this->server->dispatch( $request ); 1597 $this->assertEquals( 200, $response->get_status() ); 1598 1599 // Check if comment 1 now has the child link. 1600 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $comment_id_1 ) ); 1601 $response = $this->server->dispatch( $request ); 1602 $this->assertEquals( 200, $response->get_status() ); 1603 $this->assertArrayHasKey( 'children', $response->get_links() ); 1604 } 1605 1606 public function test_delete_item() { 1607 wp_set_current_user( $this->admin_id ); 1608 1609 $comment_id = $this->factory->comment->create( array( 1610 'comment_approved' => 1, 1611 'comment_post_ID' => $this->post_id, 1612 'user_id' => $this->subscriber_id, 1613 )); 1614 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/comments/%d', $comment_id ) ); 1615 1616 $response = $this->server->dispatch( $request ); 1617 $this->assertEquals( 200, $response->get_status() ); 1618 $data = $response->get_data(); 1619 $this->assertEquals( $this->post_id, $data['post'] ); 1620 } 1621 1622 public function test_delete_item_skip_trash() { 1623 wp_set_current_user( $this->admin_id ); 1624 1625 $comment_id = $this->factory->comment->create( array( 1626 'comment_approved' => 1, 1627 'comment_post_ID' => $this->post_id, 1628 'user_id' => $this->subscriber_id, 1629 )); 1630 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/comments/%d', $comment_id ) ); 1631 $request['force'] = true; 1632 1633 $response = $this->server->dispatch( $request ); 1634 $this->assertEquals( 200, $response->get_status() ); 1635 $data = $response->get_data(); 1636 $this->assertEquals( $this->post_id, $data['post'] ); 1637 } 1638 1639 public function test_delete_item_already_trashed() { 1640 wp_set_current_user( $this->admin_id ); 1641 1642 $comment_id = $this->factory->comment->create( array( 1643 'comment_approved' => 1, 1644 'comment_post_ID' => $this->post_id, 1645 'user_id' => $this->subscriber_id, 1646 )); 1647 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/comments/%d', $comment_id ) ); 1648 $response = $this->server->dispatch( $request ); 1649 $this->assertEquals( 200, $response->get_status() ); 1650 $data = $response->get_data(); 1651 $response = $this->server->dispatch( $request ); 1652 $this->assertErrorResponse( 'rest_already_trashed', $response, 410 ); 1653 } 1654 1655 public function test_delete_comment_invalid_id() { 1656 wp_set_current_user( $this->admin_id ); 1657 1658 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/comments/%d', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ) ); 1659 1660 $response = $this->server->dispatch( $request ); 1661 $this->assertErrorResponse( 'rest_comment_invalid_id', $response, 404 ); 1662 } 1663 1664 public function test_delete_comment_without_permission() { 1665 wp_set_current_user( $this->subscriber_id ); 1666 1667 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 1668 1669 $response = $this->server->dispatch( $request ); 1670 $this->assertErrorResponse( 'rest_cannot_delete', $response, 403 ); 1671 } 1672 1673 public function test_delete_child_comment_link() { 1674 wp_set_current_user( $this->admin_id ); 1675 $comment_id_1 = $this->factory->comment->create( array( 1676 'comment_approved' => 1, 1677 'comment_post_ID' => $this->post_id, 1678 'user_id' => $this->subscriber_id, 1679 ) ); 1680 1681 $child_comment = $this->factory->comment->create( array( 1682 'comment_approved' => 1, 1683 'comment_parent' => $comment_id_1, 1684 'comment_post_ID' => $this->post_id, 1685 'user_id' => $this->subscriber_id, 1686 ) ); 1687 1688 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/comments/%s', $child_comment ) ); 1689 $response = $this->server->dispatch( $request ); 1690 $this->assertEquals( 200, $response->get_status() ); 1691 1692 // Verify children link is gone. 1693 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $comment_id_1 ) ); 1694 $response = $this->server->dispatch( $request ); 1695 $this->assertEquals( 200, $response->get_status() ); 1696 $this->assertArrayNotHasKey( 'children', $response->get_links() ); 1697 } 1698 1699 public function test_get_item_schema() { 1700 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/comments' ); 1701 $response = $this->server->dispatch( $request ); 1702 $data = $response->get_data(); 1703 $properties = $data['schema']['properties']; 1704 $this->assertEquals( 18, count( $properties ) ); 1705 $this->assertArrayHasKey( 'id', $properties ); 1706 $this->assertArrayHasKey( 'author', $properties ); 1707 $this->assertArrayHasKey( 'author_avatar_urls', $properties ); 1708 $this->assertArrayHasKey( 'author_email', $properties ); 1709 $this->assertArrayHasKey( 'author_ip', $properties ); 1710 $this->assertArrayHasKey( 'author_name', $properties ); 1711 $this->assertArrayHasKey( 'author_url', $properties ); 1712 $this->assertArrayHasKey( 'author_user_agent', $properties ); 1713 $this->assertArrayHasKey( 'content', $properties ); 1714 $this->assertArrayHasKey( 'date', $properties ); 1715 $this->assertArrayHasKey( 'date_gmt', $properties ); 1716 $this->assertArrayHasKey( 'karma', $properties ); 1717 $this->assertArrayHasKey( 'link', $properties ); 1718 $this->assertArrayHasKey( 'meta', $properties ); 1719 $this->assertArrayHasKey( 'parent', $properties ); 1720 $this->assertArrayHasKey( 'post', $properties ); 1721 $this->assertArrayHasKey( 'status', $properties ); 1722 $this->assertArrayHasKey( 'type', $properties ); 1723 } 1724 1725 public function test_get_item_schema_show_avatar() { 1726 update_option( 'show_avatars', false ); 1727 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/users' ); 1728 $response = $this->server->dispatch( $request ); 1729 $data = $response->get_data(); 1730 $properties = $data['schema']['properties']; 1731 1732 $this->assertArrayNotHasKey( 'author_avatar_urls', $properties ); 1733 } 1734 1735 public function test_get_additional_field_registration() { 1736 1737 $schema = array( 1738 'type' => 'integer', 1739 'description' => 'Some integer of mine', 1740 'enum' => array( 1, 2, 3, 4 ), 1741 'context' => array( 'view', 'edit' ), 1742 ); 1743 1744 register_rest_field( 'comment', 'my_custom_int', array( 1745 'schema' => $schema, 1746 'get_callback' => array( $this, 'additional_field_get_callback' ), 1747 'update_callback' => array( $this, 'additional_field_update_callback' ), 1748 ) ); 1749 1750 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/comments' ); 1751 1752 $response = $this->server->dispatch( $request ); 1753 $data = $response->get_data(); 1754 1755 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 1756 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 1757 1758 $request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . $this->approved_id ); 1759 1760 $response = $this->server->dispatch( $request ); 1761 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 1762 1763 $request = new WP_REST_Request( 'POST', '/wp/v2/comments/' . $this->approved_id ); 1764 $request->set_body_params(array( 1765 'my_custom_int' => 123, 1766 'content' => 'abc', 1767 )); 1768 1769 wp_set_current_user( 1 ); 1770 $this->server->dispatch( $request ); 1771 $this->assertEquals( 123, get_comment_meta( $this->approved_id, 'my_custom_int', true ) ); 1772 1773 $request = new WP_REST_Request( 'POST', '/wp/v2/comments' ); 1774 $request->set_body_params(array( 1775 'my_custom_int' => 123, 1776 'title' => 'hello', 1777 'content' => 'goodbye', 1778 'post' => $this->post_id, 1779 )); 1780 1781 $response = $this->server->dispatch( $request ); 1782 1783 $this->assertEquals( 123, $response->data['my_custom_int'] ); 1784 1785 global $wp_rest_additional_fields; 1786 $wp_rest_additional_fields = array(); 1787 } 1788 1789 public function test_additional_field_update_errors() { 1790 $schema = array( 1791 'type' => 'integer', 1792 'description' => 'Some integer of mine', 1793 'enum' => array( 1, 2, 3, 4 ), 1794 'context' => array( 'view', 'edit' ), 1795 ); 1796 1797 register_rest_field( 'comment', 'my_custom_int', array( 1798 'schema' => $schema, 1799 'get_callback' => array( $this, 'additional_field_get_callback' ), 1800 'update_callback' => array( $this, 'additional_field_update_callback' ), 1801 ) ); 1802 1803 wp_set_current_user( $this->admin_id ); 1804 1805 // Check for error on update. 1806 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/comments/%d', $this->approved_id ) ); 1807 $request->set_body_params(array( 1808 'my_custom_int' => 'returnError', 1809 'content' => 'abc', 1810 )); 1811 1812 $response = $this->server->dispatch( $request ); 1813 1814 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1815 1816 global $wp_rest_additional_fields; 1817 $wp_rest_additional_fields = array(); 1818 } 1819 1820 public function additional_field_get_callback( $object ) { 1821 return get_comment_meta( $object['id'], 'my_custom_int', true ); 1822 } 1823 1824 public function additional_field_update_callback( $value, $comment ) { 1825 if ( 'returnError' === $value ) { 1826 return new WP_Error( 'rest_invalid_param', 'Testing an error.', array( 'status' => 400 ) ); 1827 } 1828 update_comment_meta( $comment->comment_ID, 'my_custom_int', $value ); 1829 } 1830 1831 protected function check_comment_data( $data, $context, $links ) { 1832 $comment = get_comment( $data['id'] ); 1833 1834 $this->assertEquals( $comment->comment_ID, $data['id'] ); 1835 $this->assertEquals( $comment->comment_post_ID, $data['post'] ); 1836 $this->assertEquals( $comment->comment_parent, $data['parent'] ); 1837 $this->assertEquals( $comment->user_id, $data['author'] ); 1838 $this->assertEquals( $comment->comment_author, $data['author_name'] ); 1839 $this->assertEquals( $comment->comment_author_url, $data['author_url'] ); 1840 $this->assertEquals( wpautop( $comment->comment_content ), $data['content']['rendered'] ); 1841 $this->assertEquals( mysql_to_rfc3339( $comment->comment_date ), $data['date'] ); 1842 $this->assertEquals( mysql_to_rfc3339( $comment->comment_date_gmt ), $data['date_gmt'] ); 1843 $this->assertEquals( get_comment_link( $comment ), $data['link'] ); 1844 $this->assertContains( 'author_avatar_urls', $data ); 1845 $this->assertEqualSets( array( 1846 'self', 1847 'collection', 1848 'up', 1849 ), array_keys( $links ) ); 1850 1851 if ( 'edit' === $context ) { 1852 $this->assertEquals( $comment->comment_author_email, $data['author_email'] ); 1853 $this->assertEquals( $comment->comment_author_IP, $data['author_ip'] ); 1854 $this->assertEquals( $comment->comment_agent, $data['author_user_agent'] ); 1855 $this->assertEquals( $comment->comment_content, $data['content']['raw'] ); 1856 $this->assertEquals( $comment->comment_karma, $data['karma'] ); 1857 } 1858 1859 if ( 'edit' !== $context ) { 1860 $this->assertArrayNotHasKey( 'author_email', $data ); 1861 $this->assertArrayNotHasKey( 'author_ip', $data ); 1862 $this->assertArrayNotHasKey( 'author_user_agent', $data ); 1863 $this->assertArrayNotHasKey( 'raw', $data['content'] ); 1864 $this->assertArrayNotHasKey( 'karma', $data ); 1865 } 1866 } 1867 } -
tests/phpunit/tests/rest-api/rest-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Controller functionality 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Controller extends WP_Test_REST_TestCase { 13 14 public function setUp() { 15 parent::setUp(); 16 $this->request = new WP_REST_Request( 'GET', '/wp/v2/testroute', array( 17 'args' => array( 18 'someinteger' => array( 19 'type' => 'integer', 20 ), 21 'someboolean' => array( 22 'type' => 'boolean', 23 ), 24 'somestring' => array( 25 'type' => 'string', 26 ), 27 'someenum' => array( 28 'type' => 'string', 29 'enum' => array( 'a' ), 30 ), 31 'somedate' => array( 32 'type' => 'string', 33 'format' => 'date-time', 34 ), 35 'someemail' => array( 36 'type' => 'string', 37 'format' => 'email', 38 ), 39 ), 40 )); 41 } 42 43 public function test_validate_schema_type_integer() { 44 45 $this->assertTrue( 46 rest_validate_request_arg( '123', $this->request, 'someinteger' ) 47 ); 48 49 $this->assertErrorResponse( 50 'rest_invalid_param', 51 rest_validate_request_arg( 'abc', $this->request, 'someinteger' ) 52 ); 53 } 54 55 public function test_validate_schema_type_boolean() { 56 57 $this->assertTrue( 58 rest_validate_request_arg( true, $this->request, 'someboolean' ) 59 ); 60 $this->assertTrue( 61 rest_validate_request_arg( false, $this->request, 'someboolean' ) 62 ); 63 64 $this->assertTrue( 65 rest_validate_request_arg( 'true', $this->request, 'someboolean' ) 66 ); 67 $this->assertTrue( 68 rest_validate_request_arg( 'TRUE', $this->request, 'someboolean' ) 69 ); 70 $this->assertTrue( 71 rest_validate_request_arg( 'false', $this->request, 'someboolean' ) 72 ); 73 $this->assertTrue( 74 rest_validate_request_arg( 'False', $this->request, 'someboolean' ) 75 ); 76 $this->assertTrue( 77 rest_validate_request_arg( '1', $this->request, 'someboolean' ) 78 ); 79 $this->assertTrue( 80 rest_validate_request_arg( '0', $this->request, 'someboolean' ) 81 ); 82 $this->assertTrue( 83 rest_validate_request_arg( 1, $this->request, 'someboolean' ) 84 ); 85 $this->assertTrue( 86 rest_validate_request_arg( 0, $this->request, 'someboolean' ) 87 ); 88 89 // Check sanitize testing. 90 $this->assertEquals( false, 91 rest_sanitize_request_arg( 'false', $this->request, 'someboolean' ) 92 ); 93 $this->assertEquals( false, 94 rest_sanitize_request_arg( '0', $this->request, 'someboolean' ) 95 ); 96 $this->assertEquals( false, 97 rest_sanitize_request_arg( 0, $this->request, 'someboolean' ) 98 ); 99 $this->assertEquals( false, 100 rest_sanitize_request_arg( 'FALSE', $this->request, 'someboolean' ) 101 ); 102 $this->assertEquals( true, 103 rest_sanitize_request_arg( 'true', $this->request, 'someboolean' ) 104 ); 105 $this->assertEquals( true, 106 rest_sanitize_request_arg( '1', $this->request, 'someboolean' ) 107 ); 108 $this->assertEquals( true, 109 rest_sanitize_request_arg( 1, $this->request, 'someboolean' ) 110 ); 111 $this->assertEquals( true, 112 rest_sanitize_request_arg( 'TRUE', $this->request, 'someboolean' ) 113 ); 114 115 $this->assertErrorResponse( 116 'rest_invalid_param', 117 rest_validate_request_arg( '123', $this->request, 'someboolean' ) 118 ); 119 } 120 121 public function test_validate_schema_type_string() { 122 123 $this->assertTrue( 124 rest_validate_request_arg( '123', $this->request, 'somestring' ) 125 ); 126 127 $this->assertErrorResponse( 128 'rest_invalid_param', 129 rest_validate_request_arg( array( 'foo' => 'bar' ), $this->request, 'somestring' ) 130 ); 131 } 132 133 public function test_validate_schema_enum() { 134 135 $this->assertTrue( 136 rest_validate_request_arg( 'a', $this->request, 'someenum' ) 137 ); 138 139 $this->assertErrorResponse( 140 'rest_invalid_param', 141 rest_validate_request_arg( 'd', $this->request, 'someenum' ) 142 ); 143 } 144 145 public function test_validate_schema_format_email() { 146 147 $this->assertTrue( 148 rest_validate_request_arg( 'joe@foo.bar', $this->request, 'someemail' ) 149 ); 150 151 $this->assertErrorResponse( 152 'rest_invalid_email', 153 rest_validate_request_arg( 'd', $this->request, 'someemail' ) 154 ); 155 } 156 157 public function test_validate_schema_format_date_time() { 158 159 $this->assertTrue( 160 rest_validate_request_arg( '2010-01-01T12:00:00', $this->request, 'somedate' ) 161 ); 162 163 $this->assertErrorResponse( 164 'rest_invalid_date', 165 rest_validate_request_arg( '2010-18-18T12:00:00', $this->request, 'somedate' ) 166 ); 167 } 168 169 public function test_get_endpoint_args_for_item_schema_description() { 170 $controller = new WP_REST_Test_Controller(); 171 $args = $controller->get_endpoint_args_for_item_schema(); 172 $this->assertEquals( 'A pretty string.', $args['somestring']['description'] ); 173 $this->assertFalse( isset( $args['someinteger']['description'] ) ); 174 } 175 176 public function test_get_endpoint_args_for_item_schema_arg_options() { 177 178 $controller = new WP_REST_Test_Controller(); 179 $args = $controller->get_endpoint_args_for_item_schema(); 180 181 $this->assertFalse( $args['someargoptions']['required'] ); 182 $this->assertEquals( '__return_true', $args['someargoptions']['sanitize_callback'] ); 183 } 184 185 public function test_get_endpoint_args_for_item_schema_default_value() { 186 187 $controller = new WP_REST_Test_Controller(); 188 189 $args = $controller->get_endpoint_args_for_item_schema(); 190 191 $this->assertEquals( 'a', $args['somedefault']['default'] ); 192 } 193 194 public $rest_the_post_filter_apply_count = 0; 195 196 public function test_get_post() { 197 $post_id = $this->factory()->post->create( array( 'post_title' => 'Original' ) ); 198 $controller = new WP_REST_Test_Controller(); 199 200 $post = $controller->get_post( $post_id ); 201 $this->assertEquals( 'Original', $post->post_title ); 202 203 $filter_apply_count = $this->rest_the_post_filter_apply_count; 204 add_filter( 'rest_the_post', array( $this, 'filter_rest_the_post_for_test_get_post' ), 10, 2 ); 205 $post = $controller->get_post( $post_id ); 206 $this->assertEquals( 'Overridden', $post->post_title ); 207 $this->assertEquals( 1 + $filter_apply_count, $this->rest_the_post_filter_apply_count ); 208 } 209 210 public function filter_rest_the_post_for_test_get_post( $post, $post_id ) { 211 $this->assertInstanceOf( 'WP_Post', $post ); 212 $this->assertInternalType( 'int', $post_id ); 213 $post->post_title = 'Overridden'; 214 $this->rest_the_post_filter_apply_count += 1; 215 return $post; 216 } 217 } -
tests/phpunit/tests/rest-api/rest-pages-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Posts_Controller functionality, used for 4 * Pages 5 * 6 * @package WordPress 7 * @subpackage REST API 8 */ 9 10 /** 11 * @group restapi 12 */ 13 class WP_Test_REST_Pages_Controller extends WP_Test_REST_Post_Type_Controller_Testcase { 14 15 public function setUp() { 16 parent::setUp(); 17 18 $this->editor_id = $this->factory->user->create( array( 19 'role' => 'editor', 20 ) ); 21 $this->author_id = $this->factory->user->create( array( 22 'role' => 'author', 23 ) ); 24 25 $this->has_setup_template = false; 26 add_filter( 'theme_page_templates', array( $this, 'filter_theme_page_templates' ) ); 27 } 28 29 public function test_register_routes() { 30 31 } 32 33 public function test_context_param() { 34 // Collection 35 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/pages' ); 36 $response = $this->server->dispatch( $request ); 37 $data = $response->get_data(); 38 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 39 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 40 // Single 41 $page_id = $this->factory->post->create( array( 'post_type' => 'page' ) ); 42 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/pages/' . $page_id ); 43 $response = $this->server->dispatch( $request ); 44 $data = $response->get_data(); 45 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 46 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 47 } 48 49 public function test_registered_query_params() { 50 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/pages' ); 51 $response = $this->server->dispatch( $request ); 52 $data = $response->get_data(); 53 $keys = array_keys( $data['endpoints'][0]['args'] ); 54 sort( $keys ); 55 $this->assertEquals( array( 56 'after', 57 'author', 58 'author_exclude', 59 'before', 60 'context', 61 'exclude', 62 'filter', 63 'include', 64 'menu_order', 65 'offset', 66 'order', 67 'orderby', 68 'page', 69 'parent', 70 'parent_exclude', 71 'per_page', 72 'search', 73 'slug', 74 'status', 75 ), $keys ); 76 } 77 78 public function test_get_items() { 79 80 } 81 82 public function test_get_items_parent_query() { 83 $id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) ); 84 $id2 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => $id1 ) ); 85 // No parent 86 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 87 $response = $this->server->dispatch( $request ); 88 $data = $response->get_data(); 89 $this->assertEquals( 2, count( $data ) ); 90 // Filter to parent 91 $request->set_param( 'parent', $id1 ); 92 $response = $this->server->dispatch( $request ); 93 $data = $response->get_data(); 94 $this->assertEquals( 1, count( $data ) ); 95 $this->assertEquals( $id2, $data[0]['id'] ); 96 } 97 98 public function test_get_items_parents_query() { 99 $id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) ); 100 $id2 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => $id1 ) ); 101 $id3 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) ); 102 $id4 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => $id3 ) ); 103 // No parent 104 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 105 $response = $this->server->dispatch( $request ); 106 $data = $response->get_data(); 107 $this->assertEquals( 4, count( $data ) ); 108 // Filter to parents 109 $request->set_param( 'parent', array( $id1, $id3 ) ); 110 $response = $this->server->dispatch( $request ); 111 $data = $response->get_data(); 112 $this->assertEquals( 2, count( $data ) ); 113 $this->assertEqualSets( array( $id2, $id4 ), wp_list_pluck( $data, 'id' ) ); 114 } 115 116 public function test_get_items_parent_exclude_query() { 117 $id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) ); 118 $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => $id1 ) ); 119 // No parent 120 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 121 $response = $this->server->dispatch( $request ); 122 $data = $response->get_data(); 123 $this->assertEquals( 2, count( $data ) ); 124 // Filter to parent 125 $request->set_param( 'parent_exclude', $id1 ); 126 $response = $this->server->dispatch( $request ); 127 $data = $response->get_data(); 128 $this->assertEquals( 1, count( $data ) ); 129 $this->assertEquals( $id1, $data[0]['id'] ); 130 } 131 132 public function test_get_items_menu_order_query() { 133 $id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) ); 134 $id2 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'menu_order' => 2 ) ); 135 $id3 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'menu_order' => 3 ) ); 136 $id4 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'menu_order' => 1 ) ); 137 // No parent 138 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 139 $response = $this->server->dispatch( $request ); 140 $data = $response->get_data(); 141 $this->assertEqualSets( array( $id1, $id2, $id3, $id4 ), wp_list_pluck( $data, 'id' ) ); 142 // Filter to menu_order 143 $request->set_param( 'menu_order', 1 ); 144 $response = $this->server->dispatch( $request ); 145 $data = $response->get_data(); 146 $this->assertEqualSets( array( $id4 ), wp_list_pluck( $data, 'id' ) ); 147 // Order by menu order 148 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 149 $request->set_param( 'order', 'asc' ); 150 $request->set_param( 'orderby', 'menu_order' ); 151 $response = $this->server->dispatch( $request ); 152 $data = $response->get_data(); 153 $this->assertEquals( $id1, $data[0]['id'] ); 154 $this->assertEquals( $id4, $data[1]['id'] ); 155 $this->assertEquals( $id2, $data[2]['id'] ); 156 $this->assertEquals( $id3, $data[3]['id'] ); 157 } 158 159 public function test_get_items_min_max_pages_query() { 160 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 161 $request->set_param( 'per_page', 0 ); 162 $response = $this->server->dispatch( $request ); 163 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 164 $data = $response->get_data(); 165 // Safe format for 4.4 and 4.5 https://core.trac.wordpress.org/ticket/35028 166 $first_error = array_shift( $data['data']['params'] ); 167 $this->assertContains( 'per_page must be between 1 (inclusive) and 100 (inclusive)', $first_error ); 168 $request->set_param( 'per_page', 101 ); 169 $response = $this->server->dispatch( $request ); 170 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 171 $data = $response->get_data(); 172 $first_error = array_shift( $data['data']['params'] ); 173 $this->assertContains( 'per_page must be between 1 (inclusive) and 100 (inclusive)', $first_error ); 174 } 175 176 public function test_get_items_private_filter_query_var() { 177 // Private query vars inaccessible to unauthorized users 178 wp_set_current_user( 0 ); 179 $page_id = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) ); 180 $draft_id = $this->factory->post->create( array( 'post_status' => 'draft', 'post_type' => 'page' ) ); 181 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 182 $request->set_param( 'filter', array( 'post_status' => 'draft' ) ); 183 $response = $this->server->dispatch( $request ); 184 $data = $response->get_data(); 185 $this->assertCount( 1, $data ); 186 $this->assertEquals( $page_id, $data[0]['id'] ); 187 // But they are accessible to authorized users 188 wp_set_current_user( $this->editor_id ); 189 $response = $this->server->dispatch( $request ); 190 $data = $response->get_data(); 191 $this->assertCount( 1, $data ); 192 $this->assertEquals( $draft_id, $data[0]['id'] ); 193 } 194 195 public function test_get_items_invalid_date() { 196 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 197 $request->set_param( 'after', rand_str() ); 198 $request->set_param( 'before', rand_str() ); 199 $response = $this->server->dispatch( $request ); 200 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 201 } 202 203 public function test_get_items_valid_date() { 204 $post1 = $this->factory->post->create( array( 'post_date' => '2016-01-15T00:00:00Z', 'post_type' => 'page' ) ); 205 $post2 = $this->factory->post->create( array( 'post_date' => '2016-01-16T00:00:00Z', 'post_type' => 'page' ) ); 206 $post3 = $this->factory->post->create( array( 'post_date' => '2016-01-17T00:00:00Z', 'post_type' => 'page' ) ); 207 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 208 $request->set_param( 'after', '2016-01-15T00:00:00Z' ); 209 $request->set_param( 'before', '2016-01-17T00:00:00Z' ); 210 $response = $this->server->dispatch( $request ); 211 $data = $response->get_data(); 212 $this->assertCount( 1, $data ); 213 $this->assertEquals( $post2, $data[0]['id'] ); 214 } 215 216 public function test_get_item() { 217 218 } 219 220 public function test_get_item_invalid_post_type() { 221 $post_id = $this->factory->post->create(); 222 $request = new WP_REST_Request( 'GET', '/wp/v2/pages/' . $post_id ); 223 $response = $this->server->dispatch( $request ); 224 $this->assertEquals( 404, $response->get_status() ); 225 } 226 227 public function test_create_item() { 228 229 } 230 231 public function test_create_item_with_template() { 232 wp_set_current_user( $this->editor_id ); 233 234 $request = new WP_REST_Request( 'POST', '/wp/v2/pages' ); 235 $params = $this->set_post_data( array( 236 'template' => 'page-my-test-template.php', 237 ) ); 238 $request->set_body_params( $params ); 239 $response = $this->server->dispatch( $request ); 240 241 $data = $response->get_data(); 242 $new_post = get_post( $data['id'] ); 243 $this->assertEquals( 'page-my-test-template.php', $data['template'] ); 244 $this->assertEquals( 'page-my-test-template.php', get_page_template_slug( $new_post->ID ) ); 245 } 246 247 public function test_create_page_with_parent() { 248 $page_id = $this->factory->post->create( array( 249 'type' => 'page', 250 ) ); 251 wp_set_current_user( $this->editor_id ); 252 253 $request = new WP_REST_Request( 'POST', '/wp/v2/pages' ); 254 $params = $this->set_post_data( array( 255 'parent' => $page_id, 256 ) ); 257 $request->set_body_params( $params ); 258 $response = $this->server->dispatch( $request ); 259 260 $this->assertEquals( 201, $response->get_status() ); 261 262 $links = $response->get_links(); 263 $this->assertArrayHasKey( 'up', $links ); 264 265 $data = $response->get_data(); 266 $new_post = get_post( $data['id'] ); 267 $this->assertEquals( $page_id, $data['parent'] ); 268 $this->assertEquals( $page_id, $new_post->post_parent ); 269 } 270 271 public function test_create_page_with_invalid_parent() { 272 wp_set_current_user( $this->editor_id ); 273 274 $request = new WP_REST_Request( 'POST', '/wp/v2/pages' ); 275 $params = $this->set_post_data( array( 276 'parent' => -1, 277 ) ); 278 $request->set_body_params( $params ); 279 $response = $this->server->dispatch( $request ); 280 281 $this->assertErrorResponse( 'rest_post_invalid_id', $response, 400 ); 282 } 283 284 public function test_update_item() { 285 286 } 287 288 public function test_delete_item() { 289 290 } 291 292 public function test_prepare_item() { 293 294 } 295 296 public function test_get_pages_params() { 297 $this->factory->post->create_many( 8, array( 298 'post_type' => 'page', 299 ) ); 300 301 $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); 302 $request->set_query_params( array( 303 'page' => 2, 304 'per_page' => 4, 305 ) ); 306 $response = $this->server->dispatch( $request ); 307 308 $this->assertEquals( 200, $response->get_status() ); 309 310 $headers = $response->get_headers(); 311 $this->assertEquals( 8, $headers['X-WP-Total'] ); 312 $this->assertEquals( 2, $headers['X-WP-TotalPages'] ); 313 314 $all_data = $response->get_data(); 315 $this->assertEquals( 4, count( $all_data ) ); 316 foreach ( $all_data as $post ) { 317 $this->assertEquals( 'page', $post['type'] ); 318 } 319 } 320 321 public function test_update_page_menu_order() { 322 323 $page_id = $this->factory->post->create( array( 324 'post_type' => 'page', 325 ) ); 326 327 wp_set_current_user( $this->editor_id ); 328 329 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/pages/%d', $page_id ) ); 330 331 $request->set_body_params( array( 332 'menu_order' => 1, 333 ) ); 334 $response = $this->server->dispatch( $request ); 335 336 $new_data = $response->get_data(); 337 $this->assertEquals( 1, $new_data['menu_order'] ); 338 } 339 340 public function test_update_page_menu_order_to_zero() { 341 342 $page_id = $this->factory->post->create( array( 343 'post_type' => 'page', 344 'menu_order' => 1, 345 ) ); 346 347 wp_set_current_user( $this->editor_id ); 348 349 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/pages/%d', $page_id ) ); 350 351 $request->set_body_params(array( 352 'menu_order' => 0, 353 )); 354 $response = $this->server->dispatch( $request ); 355 356 $new_data = $response->get_data(); 357 $this->assertEquals( 0, $new_data['menu_order'] ); 358 } 359 360 public function test_get_item_schema() { 361 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/pages' ); 362 $response = $this->server->dispatch( $request ); 363 $data = $response->get_data(); 364 $properties = $data['schema']['properties']; 365 $this->assertEquals( 21, count( $properties ) ); 366 $this->assertArrayHasKey( 'author', $properties ); 367 $this->assertArrayHasKey( 'comment_status', $properties ); 368 $this->assertArrayHasKey( 'content', $properties ); 369 $this->assertArrayHasKey( 'date', $properties ); 370 $this->assertArrayHasKey( 'date_gmt', $properties ); 371 $this->assertArrayHasKey( 'guid', $properties ); 372 $this->assertArrayHasKey( 'excerpt', $properties ); 373 $this->assertArrayHasKey( 'featured_media', $properties ); 374 $this->assertArrayHasKey( 'id', $properties ); 375 $this->assertArrayHasKey( 'link', $properties ); 376 $this->assertArrayHasKey( 'menu_order', $properties ); 377 $this->assertArrayHasKey( 'meta', $properties ); 378 $this->assertArrayHasKey( 'modified', $properties ); 379 $this->assertArrayHasKey( 'modified_gmt', $properties ); 380 $this->assertArrayHasKey( 'parent', $properties ); 381 $this->assertArrayHasKey( 'ping_status', $properties ); 382 $this->assertArrayHasKey( 'slug', $properties ); 383 $this->assertArrayHasKey( 'status', $properties ); 384 $this->assertArrayHasKey( 'template', $properties ); 385 $this->assertArrayHasKey( 'title', $properties ); 386 $this->assertArrayHasKey( 'type', $properties ); 387 } 388 389 public function tearDown() { 390 parent::tearDown(); 391 remove_filter( 'theme_page_templates', array( $this, 'filter_theme_page_templates' ) ); 392 } 393 394 public function filter_theme_page_templates( $page_templates ) { 395 return array( 396 'page-my-test-template.php' => 'My Test Template', 397 ); 398 return $page_templates; 399 } 400 401 protected function set_post_data( $args = array() ) { 402 $args = parent::set_post_data( $args ); 403 $args['type'] = 'page'; 404 return $args; 405 } 406 407 } -
tests/phpunit/tests/rest-api/rest-post-meta-fields.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Posts meta functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase { 13 public function setUp() { 14 parent::setUp(); 15 16 register_meta( 'post', 'test_single', array( 17 'show_in_rest' => true, 18 'single' => true, 19 )); 20 register_meta( 'post', 'test_multi', array( 21 'show_in_rest' => true, 22 'single' => false, 23 )); 24 register_meta( 'post', 'test_bad_auth', array( 25 'show_in_rest' => true, 26 'single' => true, 27 'auth_callback' => '__return_false', 28 )); 29 register_meta( 'post', 'test_bad_auth_multi', array( 30 'show_in_rest' => true, 31 'single' => false, 32 'auth_callback' => '__return_false', 33 )); 34 register_meta( 'post', 'test_no_rest', array() ); 35 register_meta( 'post', 'test_rest_disabled', array( 36 'show_in_rest' => false, 37 )); 38 register_meta( 'post', 'test_custom_schema', array( 39 'single' => true, 40 'type' => 'integer', 41 'show_in_rest' => array( 42 'schema' => array( 43 'type' => 'number', 44 ), 45 ), 46 )); 47 register_meta( 'post', 'test_invalid_type', array( 48 'single' => true, 49 'type' => false, 50 'show_in_rest' => true, 51 )); 52 53 /** @var WP_REST_Server $wp_rest_server */ 54 global $wp_rest_server; 55 $this->server = $wp_rest_server = new WP_Test_Spy_REST_Server; 56 do_action( 'rest_api_init' ); 57 58 $this->post_id = $this->factory->post->create(); 59 } 60 61 protected function grant_write_permission() { 62 // Ensure we have write permission. 63 $user = $this->factory->user->create( array( 64 'role' => 'editor', 65 )); 66 wp_set_current_user( $user ); 67 } 68 69 public function test_get_value() { 70 add_post_meta( $this->post_id, 'test_single', 'testvalue' ); 71 72 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 73 $response = $this->server->dispatch( $request ); 74 75 $this->assertEquals( 200, $response->get_status() ); 76 77 $data = $response->get_data(); 78 $this->assertArrayHasKey( 'meta', $data ); 79 80 $meta = (array) $data['meta']; 81 $this->assertArrayHasKey( 'test_single', $meta ); 82 $this->assertEquals( 'testvalue', $meta['test_single'] ); 83 } 84 85 /** 86 * @depends test_get_value 87 */ 88 public function test_get_multi_value() { 89 add_post_meta( $this->post_id, 'test_multi', 'value1' ); 90 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 91 92 $response = $this->server->dispatch( $request ); 93 $this->assertEquals( 200, $response->get_status() ); 94 95 $data = $response->get_data(); 96 $meta = (array) $data['meta']; 97 $this->assertArrayHasKey( 'test_multi', $meta ); 98 $this->assertInternalType( 'array', $meta['test_multi'] ); 99 $this->assertContains( 'value1', $meta['test_multi'] ); 100 101 // Check after an update. 102 add_post_meta( $this->post_id, 'test_multi', 'value2' ); 103 104 $response = $this->server->dispatch( $request ); 105 $this->assertEquals( 200, $response->get_status() ); 106 $data = $response->get_data(); 107 $meta = (array) $data['meta']; 108 $this->assertContains( 'value1', $meta['test_multi'] ); 109 $this->assertContains( 'value2', $meta['test_multi'] ); 110 } 111 112 /** 113 * @depends test_get_value 114 */ 115 public function test_get_unregistered() { 116 add_post_meta( $this->post_id, 'test_unregistered', 'value1' ); 117 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 118 119 $response = $this->server->dispatch( $request ); 120 $this->assertEquals( 200, $response->get_status() ); 121 122 $data = $response->get_data(); 123 $meta = (array) $data['meta']; 124 $this->assertArrayNotHasKey( 'test_unregistered', $meta ); 125 } 126 127 /** 128 * @depends test_get_value 129 */ 130 public function test_get_registered_no_api_access() { 131 add_post_meta( $this->post_id, 'test_no_rest', 'for_the_wicked' ); 132 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 133 134 $response = $this->server->dispatch( $request ); 135 $this->assertEquals( 200, $response->get_status() ); 136 137 $data = $response->get_data(); 138 $meta = (array) $data['meta']; 139 $this->assertArrayNotHasKey( 'test_no_rest', $meta ); 140 } 141 142 /** 143 * @depends test_get_value 144 */ 145 public function test_get_registered_api_disabled() { 146 add_post_meta( $this->post_id, 'test_rest_disabled', 'sleepless_nights' ); 147 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 148 149 $response = $this->server->dispatch( $request ); 150 $this->assertEquals( 200, $response->get_status() ); 151 152 $data = $response->get_data(); 153 $meta = (array) $data['meta']; 154 $this->assertArrayNotHasKey( 'test_rest_disabled', $meta ); 155 } 156 157 public function test_get_value_types() { 158 register_meta( 'post', 'test_string', array( 159 'show_in_rest' => true, 160 'single' => true, 161 'type' => 'string', 162 )); 163 register_meta( 'post', 'test_number', array( 164 'show_in_rest' => true, 165 'single' => true, 166 'type' => 'number', 167 )); 168 register_meta( 'post', 'test_bool', array( 169 'show_in_rest' => true, 170 'single' => true, 171 'type' => 'boolean', 172 )); 173 174 /** @var WP_REST_Server $wp_rest_server */ 175 global $wp_rest_server; 176 $this->server = $wp_rest_server = new WP_Test_Spy_REST_Server; 177 do_action( 'rest_api_init' ); 178 179 add_post_meta( $this->post_id, 'test_string', 42 ); 180 add_post_meta( $this->post_id, 'test_number', '42' ); 181 add_post_meta( $this->post_id, 'test_bool', 1 ); 182 183 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 184 $response = $this->server->dispatch( $request ); 185 $this->assertEquals( 200, $response->get_status() ); 186 187 $data = $response->get_data(); 188 $meta = (array) $data['meta']; 189 190 $this->assertArrayHasKey( 'test_string', $meta ); 191 $this->assertInternalType( 'string', $meta['test_string'] ); 192 $this->assertSame( '42', $meta['test_string'] ); 193 194 $this->assertArrayHasKey( 'test_number', $meta ); 195 $this->assertInternalType( 'float', $meta['test_number'] ); 196 $this->assertSame( 42.0, $meta['test_number'] ); 197 198 $this->assertArrayHasKey( 'test_bool', $meta ); 199 $this->assertInternalType( 'boolean', $meta['test_bool'] ); 200 $this->assertSame( true, $meta['test_bool'] ); 201 } 202 203 /** 204 * @depends test_get_value 205 */ 206 public function test_set_value() { 207 // Ensure no data exists currently. 208 $values = get_post_meta( $this->post_id, 'test_single', false ); 209 $this->assertEmpty( $values ); 210 211 $this->grant_write_permission(); 212 213 $data = array( 214 'meta' => array( 215 'test_single' => 'test_value', 216 ), 217 ); 218 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 219 $request->set_body_params( $data ); 220 221 $response = $this->server->dispatch( $request ); 222 $this->assertEquals( 200, $response->get_status() ); 223 224 $meta = get_post_meta( $this->post_id, 'test_single', false ); 225 $this->assertNotEmpty( $meta ); 226 $this->assertCount( 1, $meta ); 227 $this->assertEquals( 'test_value', $meta[0] ); 228 229 $data = $response->get_data(); 230 $meta = (array) $data['meta']; 231 $this->assertArrayHasKey( 'test_single', $meta ); 232 $this->assertEquals( 'test_value', $meta['test_single'] ); 233 } 234 235 /** 236 * @depends test_get_value 237 */ 238 public function test_set_duplicate_single_value() { 239 // Start with an existing metakey and value. 240 $values = update_post_meta( $this->post_id, 'test_single', 'test_value' ); 241 $this->assertEquals( 'test_value', get_post_meta( $this->post_id, 'test_single', true ) ); 242 243 $this->grant_write_permission(); 244 245 $data = array( 246 'meta' => array( 247 'test_single' => 'test_value', 248 ), 249 ); 250 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 251 $request->set_body_params( $data ); 252 253 $response = $this->server->dispatch( $request ); 254 $this->assertEquals( 200, $response->get_status() ); 255 256 $meta = get_post_meta( $this->post_id, 'test_single', true ); 257 $this->assertNotEmpty( $meta ); 258 $this->assertEquals( 'test_value', $meta ); 259 260 $data = $response->get_data(); 261 $meta = (array) $data['meta']; 262 $this->assertArrayHasKey( 'test_single', $meta ); 263 $this->assertEquals( 'test_value', $meta['test_single'] ); 264 } 265 266 /** 267 * @depends test_set_value 268 */ 269 public function test_set_value_unauthenticated() { 270 $data = array( 271 'meta' => array( 272 'test_single' => 'test_value', 273 ), 274 ); 275 276 wp_set_current_user( 0 ); 277 278 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 279 $request->set_body_params( $data ); 280 281 $response = $this->server->dispatch( $request ); 282 $this->assertErrorResponse( 'rest_cannot_edit', $response, 401 ); 283 284 // Check that the value wasn't actually updated. 285 $this->assertEmpty( get_post_meta( $this->post_id, 'test_single', false ) ); 286 } 287 288 /** 289 * @depends test_set_value 290 */ 291 public function test_set_value_blocked() { 292 $data = array( 293 'meta' => array( 294 'test_bad_auth' => 'test_value', 295 ), 296 ); 297 298 $this->grant_write_permission(); 299 300 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 301 $request->set_body_params( $data ); 302 303 $response = $this->server->dispatch( $request ); 304 $this->assertErrorResponse( 'rest_cannot_update', $response, 403 ); 305 $this->assertEmpty( get_post_meta( $this->post_id, 'test_bad_auth', false ) ); 306 } 307 308 /** 309 * @depends test_set_value 310 */ 311 public function test_set_value_db_error() { 312 $data = array( 313 'meta' => array( 314 'test_single' => 'test_value', 315 ), 316 ); 317 318 $this->grant_write_permission(); 319 320 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 321 $request->set_body_params( $data ); 322 323 /** 324 * Disable showing error as the below is going to intentionally 325 * trigger a DB error. 326 */ 327 global $wpdb; 328 $wpdb->suppress_errors = true; 329 add_filter( 'query', array( $this, 'error_insert_query' ) ); 330 331 $response = $this->server->dispatch( $request ); 332 remove_filter( 'query', array( $this, 'error_insert_query' ) ); 333 $wpdb->show_errors = true; 334 } 335 336 public function test_set_value_multiple() { 337 // Ensure no data exists currently. 338 $values = get_post_meta( $this->post_id, 'test_multi', false ); 339 $this->assertEmpty( $values ); 340 341 $this->grant_write_permission(); 342 343 $data = array( 344 'meta' => array( 345 'test_multi' => array( 'val1' ), 346 ), 347 ); 348 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 349 $request->set_body_params( $data ); 350 351 $response = $this->server->dispatch( $request ); 352 $this->assertEquals( 200, $response->get_status() ); 353 354 $meta = get_post_meta( $this->post_id, 'test_multi', false ); 355 $this->assertNotEmpty( $meta ); 356 $this->assertCount( 1, $meta ); 357 $this->assertEquals( 'val1', $meta[0] ); 358 359 // Add another value. 360 $data = array( 361 'meta' => array( 362 'test_multi' => array( 'val1', 'val2' ), 363 ), 364 ); 365 $request->set_body_params( $data ); 366 367 $response = $this->server->dispatch( $request ); 368 $this->assertEquals( 200, $response->get_status() ); 369 370 $meta = get_post_meta( $this->post_id, 'test_multi', false ); 371 $this->assertNotEmpty( $meta ); 372 $this->assertCount( 2, $meta ); 373 $this->assertContains( 'val1', $meta ); 374 $this->assertContains( 'val2', $meta ); 375 } 376 377 /** 378 * Test removing only one item with duplicate items. 379 */ 380 public function test_set_value_remove_one() { 381 add_post_meta( $this->post_id, 'test_multi', 'c' ); 382 add_post_meta( $this->post_id, 'test_multi', 'n' ); 383 add_post_meta( $this->post_id, 'test_multi', 'n' ); 384 385 $this->grant_write_permission(); 386 387 $data = array( 388 'meta' => array( 389 'test_multi' => array( 'c', 'n' ), 390 ), 391 ); 392 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 393 $request->set_body_params( $data ); 394 395 $response = $this->server->dispatch( $request ); 396 $this->assertEquals( 200, $response->get_status() ); 397 398 $meta = get_post_meta( $this->post_id, 'test_multi', false ); 399 $this->assertNotEmpty( $meta ); 400 $this->assertCount( 2, $meta ); 401 $this->assertContains( 'c', $meta ); 402 $this->assertContains( 'n', $meta ); 403 } 404 405 /** 406 * @depends test_set_value_multiple 407 */ 408 public function test_set_value_multiple_unauthenticated() { 409 // Ensure no data exists currently. 410 $values = get_post_meta( $this->post_id, 'test_multi', false ); 411 $this->assertEmpty( $values ); 412 413 wp_set_current_user( 0 ); 414 415 $data = array( 416 'meta' => array( 417 'test_multi' => array( 'val1' ), 418 ), 419 ); 420 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 421 $request->set_body_params( $data ); 422 423 $response = $this->server->dispatch( $request ); 424 $this->assertErrorResponse( 'rest_cannot_edit', $response, 401 ); 425 426 $meta = get_post_meta( $this->post_id, 'test_multi', false ); 427 $this->assertEmpty( $meta ); 428 } 429 430 /** 431 * @depends test_set_value_multiple 432 */ 433 public function test_set_value_multiple_blocked() { 434 $data = array( 435 'meta' => array( 436 'test_bad_auth_multi' => array( 'test_value' ), 437 ), 438 ); 439 440 $this->grant_write_permission(); 441 442 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 443 $request->set_body_params( $data ); 444 445 $response = $this->server->dispatch( $request ); 446 $this->assertErrorResponse( 'rest_cannot_update', $response, 403 ); 447 $this->assertEmpty( get_post_meta( $this->post_id, 'test_bad_auth_multi', false ) ); 448 } 449 450 public function test_add_multi_value_db_error() { 451 // Ensure no data exists currently. 452 $values = get_post_meta( $this->post_id, 'test_multi', false ); 453 $this->assertEmpty( $values ); 454 455 $this->grant_write_permission(); 456 457 $data = array( 458 'meta' => array( 459 'test_multi' => array( 'val1' ), 460 ), 461 ); 462 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 463 $request->set_body_params( $data ); 464 465 /** 466 * Disable showing error as the below is going to intentionally 467 * trigger a DB error. 468 */ 469 global $wpdb; 470 $wpdb->suppress_errors = true; 471 add_filter( 'query', array( $this, 'error_insert_query' ) ); 472 473 $response = $this->server->dispatch( $request ); 474 remove_filter( 'query', array( $this, 'error_insert_query' ) ); 475 $wpdb->show_errors = true; 476 477 $this->assertErrorResponse( 'rest_meta_database_error', $response, 500 ); 478 } 479 480 public function test_remove_multi_value_db_error() { 481 add_post_meta( $this->post_id, 'test_multi', 'val1' ); 482 $values = get_post_meta( $this->post_id, 'test_multi', false ); 483 $this->assertEquals( array( 'val1' ), $values ); 484 485 $this->grant_write_permission(); 486 487 $data = array( 488 'meta' => array( 489 'test_multi' => array(), 490 ), 491 ); 492 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 493 $request->set_body_params( $data ); 494 495 /** 496 * Disable showing error as the below is going to intentionally 497 * trigger a DB error. 498 */ 499 global $wpdb; 500 $wpdb->suppress_errors = true; 501 add_filter( 'query', array( $this, 'error_delete_query' ) ); 502 503 $response = $this->server->dispatch( $request ); 504 remove_filter( 'query', array( $this, 'error_delete_query' ) ); 505 $wpdb->show_errors = true; 506 507 $this->assertErrorResponse( 'rest_meta_database_error', $response, 500 ); 508 } 509 510 public function test_delete_value() { 511 add_post_meta( $this->post_id, 'test_single', 'val1' ); 512 $current = get_post_meta( $this->post_id, 'test_single', true ); 513 $this->assertEquals( 'val1', $current ); 514 515 $this->grant_write_permission(); 516 517 $data = array( 518 'meta' => array( 519 'test_single' => null, 520 ), 521 ); 522 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 523 $request->set_body_params( $data ); 524 525 $response = $this->server->dispatch( $request ); 526 $this->assertEquals( 200, $response->get_status() ); 527 528 $meta = get_post_meta( $this->post_id, 'test_single', false ); 529 $this->assertEmpty( $meta ); 530 } 531 532 /** 533 * @depends test_delete_value 534 */ 535 public function test_delete_value_blocked() { 536 add_post_meta( $this->post_id, 'test_bad_auth', 'val1' ); 537 $current = get_post_meta( $this->post_id, 'test_bad_auth', true ); 538 $this->assertEquals( 'val1', $current ); 539 540 $this->grant_write_permission(); 541 542 $data = array( 543 'meta' => array( 544 'test_bad_auth' => null, 545 ), 546 ); 547 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 548 $request->set_body_params( $data ); 549 550 $response = $this->server->dispatch( $request ); 551 $this->assertErrorResponse( 'rest_cannot_delete', $response, 403 ); 552 553 $meta = get_post_meta( $this->post_id, 'test_bad_auth', true ); 554 $this->assertEquals( 'val1', $meta ); 555 } 556 557 /** 558 * @depends test_delete_value 559 */ 560 public function test_delete_value_db_error() { 561 add_post_meta( $this->post_id, 'test_single', 'val1' ); 562 $current = get_post_meta( $this->post_id, 'test_single', true ); 563 $this->assertEquals( 'val1', $current ); 564 565 $this->grant_write_permission(); 566 567 $data = array( 568 'meta' => array( 569 'test_single' => null, 570 ), 571 ); 572 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 573 $request->set_body_params( $data ); 574 /** 575 * Disable showing error as the below is going to intentionally 576 * trigger a DB error. 577 */ 578 global $wpdb; 579 $wpdb->suppress_errors = true; 580 add_filter( 'query', array( $this, 'error_delete_query' ) ); 581 582 $response = $this->server->dispatch( $request ); 583 remove_filter( 'query', array( $this, 'error_delete_query' ) ); 584 $wpdb->show_errors = true; 585 586 $this->assertErrorResponse( 'rest_meta_database_error', $response, 500 ); 587 } 588 589 public function test_get_schema() { 590 $request = new WP_REST_Request( 'OPTIONS', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 591 $response = $this->server->dispatch( $request ); 592 593 $data = $response->get_data(); 594 $schema = $data['schema']; 595 596 $this->assertArrayHasKey( 'meta', $schema['properties'] ); 597 $meta_schema = $schema['properties']['meta']['properties']; 598 599 $this->assertArrayHasKey( 'test_single', $meta_schema ); 600 $this->assertEquals( 'string', $meta_schema['test_single']['type'] ); 601 602 $this->assertArrayHasKey( 'test_multi', $meta_schema ); 603 $this->assertEquals( 'array', $meta_schema['test_multi']['type'] ); 604 $this->assertArrayHasKey( 'items', $meta_schema['test_multi'] ); 605 $this->assertEquals( 'string', $meta_schema['test_multi']['items']['type'] ); 606 607 $this->assertArrayHasKey( 'test_custom_schema', $meta_schema ); 608 $this->assertEquals( 'number', $meta_schema['test_custom_schema']['type'] ); 609 610 $this->assertArrayNotHasKey( 'test_no_rest', $meta_schema ); 611 $this->assertArrayNotHasKey( 'test_rest_disabled', $meta_schema ); 612 $this->assertArrayNotHasKey( 'test_invalid_type', $meta_schema ); 613 } 614 615 /** 616 * Internal function used to disable an insert query which 617 * will trigger a wpdb error for testing purposes. 618 */ 619 public function error_insert_query( $query ) { 620 if ( strpos( $query, 'INSERT' ) === 0 ) { 621 $query = '],'; 622 } 623 return $query; 624 } 625 626 /** 627 * Internal function used to disable an insert query which 628 * will trigger a wpdb error for testing purposes. 629 */ 630 public function error_delete_query( $query ) { 631 if ( strpos( $query, 'DELETE' ) === 0 ) { 632 $query = '],'; 633 } 634 return $query; 635 } 636 } -
tests/phpunit/tests/rest-api/rest-post-statuses-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Posts_Statuses_Controller functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Post_Statuses_Controller extends WP_Test_REST_Controller_Testcase { 13 14 public function test_register_routes() { 15 $routes = $this->server->get_routes(); 16 $this->assertArrayHasKey( '/wp/v2/statuses', $routes ); 17 $this->assertArrayHasKey( '/wp/v2/statuses/(?P<status>[\w-]+)', $routes ); 18 } 19 20 public function test_context_param() { 21 // Collection 22 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/statuses' ); 23 $response = $this->server->dispatch( $request ); 24 $data = $response->get_data(); 25 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 26 $this->assertEqualSets( array( 'embed', 'view', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 27 // Single 28 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/statuses/publish' ); 29 $response = $this->server->dispatch( $request ); 30 $data = $response->get_data(); 31 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 32 $this->assertEqualSets( array( 'embed', 'view', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 33 } 34 35 public function test_get_items() { 36 $request = new WP_REST_Request( 'GET', '/wp/v2/statuses' ); 37 $response = $this->server->dispatch( $request ); 38 39 $data = $response->get_data(); 40 $statuses = get_post_stati( array( 'public' => true ), 'objects' ); 41 $this->assertEquals( 1, count( $data ) ); 42 $this->assertEquals( 'publish', $data['publish']['slug'] ); 43 } 44 45 public function test_get_items_logged_in() { 46 $user_id = $this->factory->user->create( array( 'role' => 'author' ) ); 47 wp_set_current_user( $user_id ); 48 49 $request = new WP_REST_Request( 'GET', '/wp/v2/statuses' ); 50 $response = $this->server->dispatch( $request ); 51 52 $data = $response->get_data(); 53 $this->assertEquals( 6, count( $data ) ); 54 $this->assertEqualSets( array( 55 'publish', 56 'private', 57 'pending', 58 'draft', 59 'trash', 60 'future', 61 ), array_keys( $data ) ); 62 } 63 64 public function test_get_items_unauthorized_context() { 65 $request = new WP_REST_Request( 'GET', '/wp/v2/statuses' ); 66 $request->set_param( 'context', 'edit' ); 67 $response = $this->server->dispatch( $request ); 68 $this->assertErrorResponse( 'rest_cannot_view', $response, 401 ); 69 } 70 71 public function test_get_item() { 72 $user_id = $this->factory->user->create( array( 'role' => 'author' ) ); 73 wp_set_current_user( $user_id ); 74 $request = new WP_REST_Request( 'GET', '/wp/v2/statuses/publish' ); 75 $request->set_param( 'context', 'edit' ); 76 $response = $this->server->dispatch( $request ); 77 $this->check_post_status_object_response( $response ); 78 } 79 80 public function test_get_item_invalid_status() { 81 $request = new WP_REST_Request( 'GET', '/wp/v2/statuses/invalid' ); 82 $response = $this->server->dispatch( $request ); 83 $this->assertErrorResponse( 'rest_status_invalid', $response, 404 ); 84 } 85 86 public function test_get_item_invalid_access() { 87 wp_set_current_user( 0 ); 88 $request = new WP_REST_Request( 'GET', '/wp/v2/statuses/draft' ); 89 $response = $this->server->dispatch( $request ); 90 $this->assertErrorResponse( 'rest_cannot_read_status', $response, 401 ); 91 } 92 93 public function test_get_item_invalid_internal() { 94 $user_id = $this->factory->user->create(); 95 wp_set_current_user( $user_id ); 96 97 $request = new WP_REST_Request( 'GET', '/wp/v2/statuses/inherit' ); 98 $response = $this->server->dispatch( $request ); 99 $this->assertErrorResponse( 'rest_cannot_read_status', $response, 403 ); 100 } 101 102 public function test_create_item() { 103 /** Post statuses can't be created **/ 104 } 105 106 public function test_update_item() { 107 /** Post statuses can't be updated **/ 108 } 109 110 public function test_delete_item() { 111 /** Post statuses can't be deleted **/ 112 } 113 114 public function test_prepare_item() { 115 $obj = get_post_status_object( 'publish' ); 116 $endpoint = new WP_REST_Post_Statuses_Controller; 117 $request = new WP_REST_Request; 118 $request->set_param( 'context', 'edit' ); 119 $data = $endpoint->prepare_item_for_response( $obj, $request ); 120 $this->check_post_status_obj( $obj, $data->get_data(), $data->get_links() ); 121 } 122 123 public function test_get_item_schema() { 124 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/statuses' ); 125 $response = $this->server->dispatch( $request ); 126 $data = $response->get_data(); 127 $properties = $data['schema']['properties']; 128 $this->assertEquals( 7, count( $properties ) ); 129 $this->assertArrayHasKey( 'name', $properties ); 130 $this->assertArrayHasKey( 'private', $properties ); 131 $this->assertArrayHasKey( 'protected', $properties ); 132 $this->assertArrayHasKey( 'public', $properties ); 133 $this->assertArrayHasKey( 'queryable', $properties ); 134 $this->assertArrayHasKey( 'show_in_list', $properties ); 135 $this->assertArrayHasKey( 'slug', $properties ); 136 } 137 138 public function test_get_additional_field_registration() { 139 140 $schema = array( 141 'type' => 'integer', 142 'description' => 'Some integer of mine', 143 'enum' => array( 1, 2, 3, 4 ), 144 'context' => array( 'view', 'edit' ), 145 ); 146 147 register_rest_field( 'status', 'my_custom_int', array( 148 'schema' => $schema, 149 'get_callback' => array( $this, 'additional_field_get_callback' ), 150 'update_callback' => array( $this, 'additional_field_update_callback' ), 151 ) ); 152 153 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/statuses' ); 154 155 $response = $this->server->dispatch( $request ); 156 $data = $response->get_data(); 157 158 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 159 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 160 161 $request = new WP_REST_Request( 'GET', '/wp/v2/statuses/publish' ); 162 163 $response = $this->server->dispatch( $request ); 164 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 165 166 global $wp_rest_additional_fields; 167 $wp_rest_additional_fields = array(); 168 } 169 170 public function additional_field_get_callback( $object ) { 171 return 123; 172 } 173 174 protected function check_post_status_obj( $status_obj, $data, $links ) { 175 $this->assertEquals( $status_obj->label, $data['name'] ); 176 $this->assertEquals( $status_obj->private, $data['private'] ); 177 $this->assertEquals( $status_obj->protected, $data['protected'] ); 178 $this->assertEquals( $status_obj->public, $data['public'] ); 179 $this->assertEquals( $status_obj->publicly_queryable, $data['queryable'] ); 180 $this->assertEquals( $status_obj->show_in_admin_all_list, $data['show_in_list'] ); 181 $this->assertEquals( $status_obj->name, $data['slug'] ); 182 $this->assertEqualSets( array( 183 'archives', 184 ), array_keys( $links ) ); 185 } 186 187 protected function check_post_status_object_response( $response ) { 188 $this->assertEquals( 200, $response->get_status() ); 189 $data = $response->get_data(); 190 $obj = get_post_status_object( 'publish' ); 191 $this->check_post_status_obj( $obj, $data, $response->get_links() ); 192 } 193 194 } -
tests/phpunit/tests/rest-api/rest-post-types-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Posts_Types_Controller functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Post_Types_Controller extends WP_Test_REST_Controller_Testcase { 13 14 public function test_register_routes() { 15 $routes = $this->server->get_routes(); 16 $this->assertArrayHasKey( '/wp/v2/types', $routes ); 17 $this->assertArrayHasKey( '/wp/v2/types/(?P<type>[\w-]+)', $routes ); 18 } 19 20 public function test_context_param() { 21 // Collection 22 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types' ); 23 $response = $this->server->dispatch( $request ); 24 $data = $response->get_data(); 25 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 26 $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); 27 // Single 28 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types/post' ); 29 $response = $this->server->dispatch( $request ); 30 $data = $response->get_data(); 31 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 32 $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); 33 } 34 35 public function test_get_items() { 36 $request = new WP_REST_Request( 'GET', '/wp/v2/types' ); 37 $response = $this->server->dispatch( $request ); 38 39 $data = $response->get_data(); 40 $post_types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); 41 $this->assertEquals( count( $post_types ), count( $data ) ); 42 $this->assertEquals( $post_types['post']->name, $data['post']['slug'] ); 43 $this->check_post_type_obj( 'view', $post_types['post'], $data['post'], $data['post']['_links'] ); 44 $this->assertEquals( $post_types['page']->name, $data['page']['slug'] ); 45 $this->check_post_type_obj( 'view', $post_types['page'], $data['page'], $data['page']['_links'] ); 46 $this->assertFalse( isset( $data['revision'] ) ); 47 } 48 49 public function test_get_items_invalid_permission_for_context() { 50 wp_set_current_user( 0 ); 51 $request = new WP_REST_Request( 'GET', '/wp/v2/types' ); 52 $request->set_param( 'context', 'edit' ); 53 $response = $this->server->dispatch( $request ); 54 $this->assertErrorResponse( 'rest_cannot_view', $response, 401 ); 55 } 56 57 public function test_get_item() { 58 $request = new WP_REST_Request( 'GET', '/wp/v2/types/post' ); 59 $response = $this->server->dispatch( $request ); 60 $this->check_post_type_object_response( 'view', $response ); 61 } 62 63 public function test_get_item_invalid_type() { 64 $request = new WP_REST_Request( 'GET', '/wp/v2/types/invalid' ); 65 $response = $this->server->dispatch( $request ); 66 $this->assertErrorResponse( 'rest_type_invalid', $response, 404 ); 67 } 68 69 public function test_get_item_edit_context() { 70 $editor_id = $this->factory->user->create( array( 'role' => 'editor' ) ); 71 wp_set_current_user( $editor_id ); 72 $request = new WP_REST_Request( 'GET', '/wp/v2/types/post' ); 73 $request->set_param( 'context', 'edit' ); 74 $response = $this->server->dispatch( $request ); 75 $this->check_post_type_object_response( 'edit', $response ); 76 } 77 78 public function test_get_item_invalid_permission_for_context() { 79 wp_set_current_user( 0 ); 80 $request = new WP_REST_Request( 'GET', '/wp/v2/types/post' ); 81 $request->set_param( 'context', 'edit' ); 82 $response = $this->server->dispatch( $request ); 83 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 84 } 85 86 public function test_create_item() { 87 /** Post types can't be created **/ 88 } 89 90 public function test_update_item() { 91 /** Post types can't be updated **/ 92 } 93 94 public function test_delete_item() { 95 /** Post types can't be deleted **/ 96 } 97 98 public function test_prepare_item() { 99 $obj = get_post_type_object( 'post' ); 100 $endpoint = new WP_REST_Post_Types_Controller; 101 $request = new WP_REST_Request; 102 $request->set_param( 'context', 'edit' ); 103 $response = $endpoint->prepare_item_for_response( $obj, $request ); 104 $this->check_post_type_obj( 'edit', $obj, $response->get_data(), $response->get_links() ); 105 } 106 107 public function test_get_item_schema() { 108 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types' ); 109 $response = $this->server->dispatch( $request ); 110 $data = $response->get_data(); 111 $properties = $data['schema']['properties']; 112 $this->assertEquals( 6, count( $properties ) ); 113 $this->assertArrayHasKey( 'capabilities', $properties ); 114 $this->assertArrayHasKey( 'description', $properties ); 115 $this->assertArrayHasKey( 'hierarchical', $properties ); 116 $this->assertArrayHasKey( 'labels', $properties ); 117 $this->assertArrayHasKey( 'name', $properties ); 118 $this->assertArrayHasKey( 'slug', $properties ); 119 } 120 121 public function test_get_additional_field_registration() { 122 123 $schema = array( 124 'type' => 'integer', 125 'description' => 'Some integer of mine', 126 'enum' => array( 1, 2, 3, 4 ), 127 'context' => array( 'view', 'edit' ), 128 ); 129 130 register_rest_field( 'type', 'my_custom_int', array( 131 'schema' => $schema, 132 'get_callback' => array( $this, 'additional_field_get_callback' ), 133 'update_callback' => array( $this, 'additional_field_update_callback' ), 134 ) ); 135 136 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types/schema' ); 137 138 $response = $this->server->dispatch( $request ); 139 $data = $response->get_data(); 140 141 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 142 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 143 144 $request = new WP_REST_Request( 'GET', '/wp/v2/types/post' ); 145 146 $response = $this->server->dispatch( $request ); 147 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 148 149 global $wp_rest_additional_fields; 150 $wp_rest_additional_fields = array(); 151 } 152 153 public function additional_field_get_callback( $object ) { 154 return 123; 155 } 156 157 protected function check_post_type_obj( $context, $post_type_obj, $data, $links ) { 158 $this->assertEquals( $post_type_obj->label, $data['name'] ); 159 $this->assertEquals( $post_type_obj->name, $data['slug'] ); 160 $this->assertEquals( $post_type_obj->description, $data['description'] ); 161 $this->assertEquals( $post_type_obj->hierarchical, $data['hierarchical'] ); 162 163 $links = test_rest_expand_compact_links( $links ); 164 $this->assertEquals( rest_url( 'wp/v2/types' ), $links['collection'][0]['href'] ); 165 $this->assertArrayHasKey( 'https://api.w.org/items', $links ); 166 if ( 'edit' === $context ) { 167 $this->assertEquals( $post_type_obj->cap, $data['capabilities'] ); 168 $this->assertEquals( $post_type_obj->labels, $data['labels'] ); 169 } else { 170 $this->assertFalse( isset( $data['capabilities'] ) ); 171 $this->assertFalse( isset( $data['labels'] ) ); 172 } 173 } 174 175 protected function check_post_type_object_response( $context, $response ) { 176 $this->assertEquals( 200, $response->get_status() ); 177 $data = $response->get_data(); 178 $obj = get_post_type_object( 'post' ); 179 $this->check_post_type_obj( $context, $obj, $data, $response->get_links() ); 180 } 181 182 } -
tests/phpunit/tests/rest-api/rest-posts-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Posts_Controller functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Testcase { 13 14 public function setUp() { 15 parent::setUp(); 16 17 $this->post_id = $this->factory->post->create(); 18 19 $this->editor_id = $this->factory->user->create( array( 20 'role' => 'editor', 21 ) ); 22 $this->author_id = $this->factory->user->create( array( 23 'role' => 'author', 24 ) ); 25 $this->contributor_id = $this->factory->user->create( array( 26 'role' => 'contributor', 27 ) ); 28 29 register_post_type( 'youseeme', array( 'supports' => array(), 'show_in_rest' => true ) ); 30 } 31 32 public function test_register_routes() { 33 $routes = $this->server->get_routes(); 34 35 $this->assertArrayHasKey( '/wp/v2/posts', $routes ); 36 $this->assertCount( 2, $routes['/wp/v2/posts'] ); 37 $this->assertArrayHasKey( '/wp/v2/posts/(?P<id>[\d]+)', $routes ); 38 $this->assertCount( 3, $routes['/wp/v2/posts/(?P<id>[\d]+)'] ); 39 } 40 41 public function test_context_param() { 42 // Collection 43 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts' ); 44 $response = $this->server->dispatch( $request ); 45 $data = $response->get_data(); 46 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 47 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 48 // Single 49 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id ); 50 $response = $this->server->dispatch( $request ); 51 $data = $response->get_data(); 52 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 53 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 54 } 55 56 public function test_registered_query_params() { 57 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts' ); 58 $response = $this->server->dispatch( $request ); 59 $data = $response->get_data(); 60 $keys = array_keys( $data['endpoints'][0]['args'] ); 61 sort( $keys ); 62 $this->assertEquals( array( 63 'after', 64 'author', 65 'author_exclude', 66 'before', 67 'categories', 68 'context', 69 'exclude', 70 'filter', 71 'include', 72 'offset', 73 'order', 74 'orderby', 75 'page', 76 'per_page', 77 'search', 78 'slug', 79 'status', 80 'sticky', 81 'tags', 82 ), $keys ); 83 } 84 85 public function test_get_items() { 86 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 87 $response = $this->server->dispatch( $request ); 88 89 $this->check_get_posts_response( $response ); 90 } 91 92 /** 93 * A valid query that returns 0 results should return an empty JSON list. 94 * 95 * @issue 862 96 */ 97 public function test_get_items_empty_query() { 98 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 99 $request->set_query_params( array( 100 'filter' => array( 'year' => 2008 ), 101 ) ); 102 $response = $this->server->dispatch( $request ); 103 $this->assertEquals( array(), $response->get_data() ); 104 $this->assertEquals( 200, $response->get_status() ); 105 } 106 107 public function test_get_items_author_query() { 108 $this->factory->post->create( array( 'post_author' => $this->editor_id ) ); 109 $this->factory->post->create( array( 'post_author' => $this->author_id ) ); 110 // All 3 posts 111 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 112 $response = $this->server->dispatch( $request ); 113 $this->assertEquals( 200, $response->get_status() ); 114 $this->assertEquals( 3, count( $response->get_data() ) ); 115 // 2 of 3 posts 116 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 117 $request->set_param( 'author', array( $this->editor_id, $this->author_id ) ); 118 $response = $this->server->dispatch( $request ); 119 $this->assertEquals( 200, $response->get_status() ); 120 $data = $response->get_data(); 121 $this->assertEquals( 2, count( $data ) ); 122 $this->assertEqualSets( array( $this->editor_id, $this->author_id ), wp_list_pluck( $data, 'author' ) ); 123 // 1 of 3 posts 124 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 125 $request->set_param( 'author', $this->editor_id ); 126 $response = $this->server->dispatch( $request ); 127 $this->assertEquals( 200, $response->get_status() ); 128 $data = $response->get_data(); 129 $this->assertEquals( 1, count( $data ) ); 130 $this->assertEquals( $this->editor_id, $data[0]['author'] ); 131 } 132 133 public function test_get_items_author_exclude_query() { 134 $this->factory->post->create( array( 'post_author' => $this->editor_id ) ); 135 $this->factory->post->create( array( 'post_author' => $this->author_id ) ); 136 // All 3 posts 137 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 138 $response = $this->server->dispatch( $request ); 139 $this->assertEquals( 200, $response->get_status() ); 140 $this->assertEquals( 3, count( $response->get_data() ) ); 141 // 1 of 3 posts 142 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 143 $request->set_param( 'author_exclude', array( $this->editor_id, $this->author_id ) ); 144 $response = $this->server->dispatch( $request ); 145 $this->assertEquals( 200, $response->get_status() ); 146 $data = $response->get_data(); 147 $this->assertEquals( 1, count( $data ) ); 148 $this->assertNotEquals( $this->editor_id, $data[0]['author'] ); 149 $this->assertNotEquals( $this->author_id, $data[0]['author'] ); 150 // 2 of 3 posts 151 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 152 $request->set_param( 'author_exclude', $this->editor_id ); 153 $response = $this->server->dispatch( $request ); 154 $this->assertEquals( 200, $response->get_status() ); 155 $data = $response->get_data(); 156 $this->assertEquals( 2, count( $data ) ); 157 $this->assertNotEquals( $this->editor_id, $data[0]['author'] ); 158 $this->assertNotEquals( $this->editor_id, $data[1]['author'] ); 159 } 160 161 public function test_get_items_include_query() { 162 $id1 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 163 $this->factory->post->create( array( 'post_status' => 'publish' ) ); 164 $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 165 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 166 // Orderby=>desc 167 $request->set_param( 'include', array( $id1, $id3 ) ); 168 $response = $this->server->dispatch( $request ); 169 $data = $response->get_data(); 170 $this->assertEquals( 2, count( $data ) ); 171 $this->assertEquals( $id3, $data[0]['id'] ); 172 // Orderby=>include 173 $request->set_param( 'orderby', 'include' ); 174 $response = $this->server->dispatch( $request ); 175 $data = $response->get_data(); 176 $this->assertEquals( 2, count( $data ) ); 177 $this->assertEquals( $id1, $data[0]['id'] ); 178 } 179 180 public function test_get_items_exclude_query() { 181 $id1 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 182 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 183 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 184 $response = $this->server->dispatch( $request ); 185 $data = $response->get_data(); 186 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 187 $this->assertTrue( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 188 $request->set_param( 'exclude', array( $id2 ) ); 189 $response = $this->server->dispatch( $request ); 190 $data = $response->get_data(); 191 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 192 $this->assertFalse( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 193 } 194 195 public function test_get_items_search_query() { 196 for ( $i = 0; $i < 5; $i++ ) { 197 $this->factory->post->create( array( 'post_status' => 'publish' ) ); 198 } 199 $this->factory->post->create( array( 'post_title' => 'Search Result', 'post_status' => 'publish' ) ); 200 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 201 $response = $this->server->dispatch( $request ); 202 $this->assertEquals( 7, count( $response->get_data() ) ); 203 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 204 $request->set_param( 'search', 'Search Result' ); 205 $response = $this->server->dispatch( $request ); 206 $data = $response->get_data(); 207 $this->assertEquals( 1, count( $data ) ); 208 $this->assertEquals( 'Search Result', $data[0]['title']['rendered'] ); 209 } 210 211 public function test_get_items_slug_query() { 212 $this->factory->post->create( array( 'post_title' => 'Apple', 'post_status' => 'publish' ) ); 213 $this->factory->post->create( array( 'post_title' => 'Banana', 'post_status' => 'publish' ) ); 214 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 215 $request->set_param( 'slug', 'apple' ); 216 $response = $this->server->dispatch( $request ); 217 $this->assertEquals( 200, $response->get_status() ); 218 $data = $response->get_data(); 219 $this->assertEquals( 1, count( $data ) ); 220 $this->assertEquals( 'Apple', $data[0]['title']['rendered'] ); 221 } 222 223 public function test_get_items_status_query() { 224 wp_set_current_user( 0 ); 225 $this->factory->post->create( array( 'post_status' => 'draft' ) ); 226 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 227 $request->set_param( 'status', 'publish' ); 228 $response = $this->server->dispatch( $request ); 229 $this->assertEquals( 200, $response->get_status() ); 230 $this->assertEquals( 1, count( $response->get_data() ) ); 231 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 232 $request->set_param( 'status', 'draft' ); 233 $response = $this->server->dispatch( $request ); 234 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 235 wp_set_current_user( $this->editor_id ); 236 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 237 $request->set_param( 'status', 'draft' ); 238 $response = $this->server->dispatch( $request ); 239 $this->assertEquals( 200, $response->get_status() ); 240 $this->assertEquals( 1, count( $response->get_data() ) ); 241 } 242 243 public function test_get_items_status_without_permissions() { 244 $draft_id = $this->factory->post->create( array( 245 'post_status' => 'draft', 246 ) ); 247 wp_set_current_user( 0 ); 248 249 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 250 $response = $this->server->dispatch( $request ); 251 252 $this->assertEquals( 200, $response->get_status() ); 253 254 $all_data = $response->get_data(); 255 foreach ( $all_data as $post ) { 256 $this->assertNotEquals( $draft_id, $post['id'] ); 257 } 258 } 259 260 public function test_get_items_order_and_orderby() { 261 $this->factory->post->create( array( 'post_title' => 'Apple Pie', 'post_status' => 'publish' ) ); 262 $this->factory->post->create( array( 'post_title' => 'Apple Sauce', 'post_status' => 'publish' ) ); 263 $this->factory->post->create( array( 'post_title' => 'Apple Cobbler', 'post_status' => 'publish' ) ); 264 $this->factory->post->create( array( 'post_title' => 'Apple Coffee Cake', 'post_status' => 'publish' ) ); 265 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 266 $request->set_param( 'search', 'Apple' ); 267 // order defaults to 'desc' 268 $request->set_param( 'orderby', 'title' ); 269 $response = $this->server->dispatch( $request ); 270 $data = $response->get_data(); 271 $this->assertEquals( 'Apple Sauce', $data[0]['title']['rendered'] ); 272 // order=>asc 273 $request->set_param( 'order', 'asc' ); 274 $response = $this->server->dispatch( $request ); 275 $data = $response->get_data(); 276 $this->assertEquals( 'Apple Cobbler', $data[0]['title']['rendered'] ); 277 } 278 279 public function test_get_items_with_orderby_relevance() { 280 $this->factory->post->create( array( 'post_title' => 'Title is more relevant', 'post_content' => 'Content is', 'post_status' => 'publish' ) ); 281 $this->factory->post->create( array( 'post_title' => 'Title is', 'post_content' => 'Content is less relevant', 'post_status' => 'publish' ) ); 282 283 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 284 $request->set_param( 'orderby', 'relevance' ); 285 $response = $this->server->dispatch( $request ); 286 $this->assertErrorResponse( 'rest_no_search_term_defined', $response, 400 ); 287 } 288 289 public function test_get_items_ignore_sticky_posts_by_default() { 290 $this->markTestSkipped( 'Broken, see https://github.com/WP-API/WP-API/issues/2210' ); 291 $post_id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_date' => '2015-01-01 12:00:00', 'post_date_gmt' => '2015-01-01 12:00:00' ) ); 292 $post_id2 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_date' => '2015-01-02 12:00:00', 'post_date_gmt' => '2015-01-02 12:00:00' ) ); 293 $post_id3 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_date' => '2015-01-03 12:00:00', 'post_date_gmt' => '2015-01-03 12:00:00' ) ); 294 stick_post( $post_id2 ); 295 296 // No stickies by default 297 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 298 $response = $this->server->dispatch( $request ); 299 $data = $response->get_data(); 300 $this->assertEquals( array( $this->post_id, $post_id3, $post_id2, $post_id1 ), wp_list_pluck( $data, 'id' ) ); 301 302 // Permit stickies 303 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 304 $request->set_param( 'filter', array( 'ignore_sticky_posts' => false ) ); 305 $response = $this->server->dispatch( $request ); 306 $data = $response->get_data(); 307 $this->assertEquals( array( $post_id2, $this->post_id, $post_id3, $post_id1 ), wp_list_pluck( $data, 'id' ) ); 308 } 309 310 public function test_get_items_offset_query() { 311 $id1 = $this->post_id; 312 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 313 $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 314 $id4 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 315 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 316 $request->set_param( 'offset', 1 ); 317 $response = $this->server->dispatch( $request ); 318 $this->assertCount( 3, $response->get_data() ); 319 // 'offset' works with 'per_page' 320 $request->set_param( 'per_page', 2 ); 321 $response = $this->server->dispatch( $request ); 322 $this->assertCount( 2, $response->get_data() ); 323 // 'offset' takes priority over 'page' 324 $request->set_param( 'page', 3 ); 325 $response = $this->server->dispatch( $request ); 326 $this->assertCount( 2, $response->get_data() ); 327 } 328 329 public function test_get_items_tags_query() { 330 $id1 = $this->post_id; 331 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 332 $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 333 $id4 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 334 $tag = wp_insert_term( 'My Tag', 'post_tag' ); 335 336 wp_set_object_terms( $id1, array( $tag['term_id'] ), 'post_tag' ); 337 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 338 $request->set_param( 'tags', array( $tag['term_id'] ) ); 339 340 $response = $this->server->dispatch( $request ); 341 $data = $response->get_data(); 342 $this->assertCount( 1, $data ); 343 $this->assertEquals( $id1, $data[0]['id'] ); 344 } 345 346 public function test_get_items_tags_exclude_query() { 347 $id1 = $this->post_id; 348 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 349 $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 350 $id4 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 351 $tag = wp_insert_term( 'My Tag', 'post_tag' ); 352 353 wp_set_object_terms( $id1, array( $tag['term_id'] ), 'post_tag' ); 354 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 355 $request->set_param( 'tags_exclude', array( $tag['term_id'] ) ); 356 357 $response = $this->server->dispatch( $request ); 358 $data = $response->get_data(); 359 $this->assertCount( 3, $data ); 360 $this->assertEquals( $id4, $data[0]['id'] ); 361 $this->assertEquals( $id3, $data[1]['id'] ); 362 $this->assertEquals( $id2, $data[2]['id'] ); 363 } 364 365 public function test_get_items_tags_and_categories_query() { 366 $id1 = $this->post_id; 367 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 368 $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 369 $id4 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 370 $tag = wp_insert_term( 'My Tag', 'post_tag' ); 371 $category = wp_insert_term( 'My Category', 'category' ); 372 373 wp_set_object_terms( $id1, array( $tag['term_id'] ), 'post_tag' ); 374 wp_set_object_terms( $id2, array( $tag['term_id'] ), 'post_tag' ); 375 wp_set_object_terms( $id1, array( $category['term_id'] ), 'category' ); 376 377 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 378 $request->set_param( 'tags', array( $tag['term_id'] ) ); 379 $request->set_param( 'categories', array( $category['term_id'] ) ); 380 381 $response = $this->server->dispatch( $request ); 382 $this->assertCount( 1, $response->get_data() ); 383 } 384 385 public function test_get_items_tags_and_categories_exclude_query() { 386 $id1 = $this->post_id; 387 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 388 $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 389 $id4 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 390 $tag = wp_insert_term( 'My Tag', 'post_tag' ); 391 $category = wp_insert_term( 'My Category', 'category' ); 392 393 wp_set_object_terms( $id1, array( $tag['term_id'] ), 'post_tag' ); 394 wp_set_object_terms( $id2, array( $tag['term_id'] ), 'post_tag' ); 395 wp_set_object_terms( $id1, array( $category['term_id'] ), 'category' ); 396 397 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 398 $request->set_param( 'tags', array( $tag['term_id'] ) ); 399 $request->set_param( 'categories_exclude', array( $category['term_id'] ) ); 400 401 $response = $this->server->dispatch( $request ); 402 $data = $response->get_data(); 403 $this->assertCount( 1, $data ); 404 $this->assertEquals( $id2, $data[0]['id'] ); 405 } 406 407 public function test_get_items_sticky_query() { 408 $id1 = $this->post_id; 409 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 410 411 update_option( 'sticky_posts', array( $id2 ) ); 412 413 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 414 $request->set_param( 'sticky', true ); 415 416 $response = $this->server->dispatch( $request ); 417 $this->assertCount( 1, $response->get_data() ); 418 419 $posts = $response->get_data(); 420 $post = $posts[0]; 421 $this->assertEquals( $id2, $post['id'] ); 422 } 423 424 public function test_get_items_sticky_with_post__in_query() { 425 $id1 = $this->post_id; 426 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 427 $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 428 429 update_option( 'sticky_posts', array( $id2 ) ); 430 431 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 432 $request->set_param( 'sticky', true ); 433 $request->set_param( 'include', array( $id1 ) ); 434 435 $response = $this->server->dispatch( $request ); 436 $this->assertCount( 0, $response->get_data() ); 437 438 update_option( 'sticky_posts', array( $id1, $id2 ) ); 439 440 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 441 $request->set_param( 'sticky', true ); 442 $request->set_param( 'include', array( $id1 ) ); 443 444 $response = $this->server->dispatch( $request ); 445 446 $this->assertCount( 1, $response->get_data() ); 447 448 $posts = $response->get_data(); 449 $post = $posts[0]; 450 $this->assertEquals( $id1, $post['id'] ); 451 } 452 453 public function test_get_items_not_sticky_query() { 454 $id1 = $this->post_id; 455 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 456 457 update_option( 'sticky_posts', array( $id2 ) ); 458 459 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 460 $request->set_param( 'sticky', false ); 461 462 $response = $this->server->dispatch( $request ); 463 $this->assertCount( 1, $response->get_data() ); 464 465 $posts = $response->get_data(); 466 $post = $posts[0]; 467 $this->assertEquals( $id1, $post['id'] ); 468 } 469 470 public function test_get_items_sticky_with_post__not_in_query() { 471 $id1 = $this->post_id; 472 $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 473 $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) ); 474 475 update_option( 'sticky_posts', array( $id2 ) ); 476 477 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 478 $request->set_param( 'sticky', false ); 479 $request->set_param( 'exclude', array( $id3 ) ); 480 481 $response = $this->server->dispatch( $request ); 482 $this->assertCount( 1, $response->get_data() ); 483 484 $posts = $response->get_data(); 485 $post = $posts[0]; 486 $this->assertEquals( $id1, $post['id'] ); 487 } 488 489 /** 490 * @group test 491 */ 492 public function test_get_items_pagination_headers() { 493 // Start of the index 494 for ( $i = 0; $i < 49; $i++ ) { 495 $this->factory->post->create( array( 496 'post_title' => "Post {$i}", 497 ) ); 498 } 499 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 500 $response = $this->server->dispatch( $request ); 501 $headers = $response->get_headers(); 502 $this->assertEquals( 50, $headers['X-WP-Total'] ); 503 $this->assertEquals( 5, $headers['X-WP-TotalPages'] ); 504 $next_link = add_query_arg( array( 505 'page' => 2, 506 ), rest_url( '/wp/v2/posts' ) ); 507 $this->assertFalse( stripos( $headers['Link'], 'rel="prev"' ) ); 508 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 509 // 3rd page 510 $this->factory->post->create( array( 511 'post_title' => 'Post 51', 512 ) ); 513 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 514 $request->set_param( 'page', 3 ); 515 $response = $this->server->dispatch( $request ); 516 $headers = $response->get_headers(); 517 $this->assertEquals( 51, $headers['X-WP-Total'] ); 518 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 519 $prev_link = add_query_arg( array( 520 'page' => 2, 521 ), rest_url( '/wp/v2/posts' ) ); 522 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 523 $next_link = add_query_arg( array( 524 'page' => 4, 525 ), rest_url( '/wp/v2/posts' ) ); 526 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 527 // Last page 528 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 529 $request->set_param( 'page', 6 ); 530 $response = $this->server->dispatch( $request ); 531 $headers = $response->get_headers(); 532 $this->assertEquals( 51, $headers['X-WP-Total'] ); 533 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 534 $prev_link = add_query_arg( array( 535 'page' => 5, 536 ), rest_url( '/wp/v2/posts' ) ); 537 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 538 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 539 // Out of bounds 540 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 541 $request->set_param( 'page', 8 ); 542 $response = $this->server->dispatch( $request ); 543 $headers = $response->get_headers(); 544 $this->assertEquals( 51, $headers['X-WP-Total'] ); 545 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 546 $prev_link = add_query_arg( array( 547 'page' => 6, 548 ), rest_url( '/wp/v2/posts' ) ); 549 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 550 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 551 552 // With query params. 553 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 554 $request->set_query_params( array( 'per_page' => 5, 'page' => 2 ) ); 555 $response = $this->server->dispatch( $request ); 556 $headers = $response->get_headers(); 557 $this->assertEquals( 51, $headers['X-WP-Total'] ); 558 $this->assertEquals( 11, $headers['X-WP-TotalPages'] ); 559 $prev_link = add_query_arg( array( 560 'per_page' => 5, 561 'page' => 1, 562 ), rest_url( '/wp/v2/posts' ) ); 563 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 564 $next_link = add_query_arg( array( 565 'per_page' => 5, 566 'page' => 3, 567 ), rest_url( '/wp/v2/posts' ) ); 568 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 569 } 570 571 public function test_get_items_private_filter_query_var() { 572 // Private query vars inaccessible to unauthorized users 573 wp_set_current_user( 0 ); 574 $draft_id = $this->factory->post->create( array( 'post_status' => 'draft' ) ); 575 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 576 $request->set_param( 'filter', array( 'post_status' => 'draft' ) ); 577 $response = $this->server->dispatch( $request ); 578 $data = $response->get_data(); 579 $this->assertCount( 1, $data ); 580 $this->assertEquals( $this->post_id, $data[0]['id'] ); 581 // But they are accessible to authorized users 582 wp_set_current_user( $this->editor_id ); 583 $response = $this->server->dispatch( $request ); 584 $data = $response->get_data(); 585 $this->assertCount( 1, $data ); 586 $this->assertEquals( $draft_id, $data[0]['id'] ); 587 } 588 589 public function test_get_items_invalid_per_page() { 590 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 591 $request->set_query_params( array( 'per_page' => -1 ) ); 592 $response = $this->server->dispatch( $request ); 593 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 594 } 595 596 public function test_get_items_invalid_posts_per_page_ignored() { 597 // This test ensures that filter[posts_per_page] is ignored, and that -1 598 // cannot be used to sidestep per_page's valid range to retrieve all posts 599 for ( $i = 0; $i < 20; $i++ ) { 600 $this->factory->post->create( array( 'post_status' => 'publish' ) ); 601 } 602 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 603 $request->set_query_params( array( 'filter' => array( 'posts_per_page' => -1 ) ) ); 604 $response = $this->server->dispatch( $request ); 605 $this->assertCount( 10, $response->get_data() ); 606 } 607 608 public function test_get_items_invalid_context() { 609 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 610 $request->set_param( 'context', 'banana' ); 611 $response = $this->server->dispatch( $request ); 612 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 613 } 614 615 public function test_get_items_invalid_date() { 616 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 617 $request->set_param( 'after', rand_str() ); 618 $request->set_param( 'before', rand_str() ); 619 $response = $this->server->dispatch( $request ); 620 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 621 } 622 623 public function test_get_items_valid_date() { 624 $post1 = $this->factory->post->create( array( 'post_date' => '2016-01-15T00:00:00Z' ) ); 625 $post2 = $this->factory->post->create( array( 'post_date' => '2016-01-16T00:00:00Z' ) ); 626 $post3 = $this->factory->post->create( array( 'post_date' => '2016-01-17T00:00:00Z' ) ); 627 628 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 629 $request->set_param( 'after', '2016-01-15T00:00:00Z' ); 630 $request->set_param( 'before', '2016-01-17T00:00:00Z' ); 631 $response = $this->server->dispatch( $request ); 632 $data = $response->get_data(); 633 $this->assertCount( 1, $data ); 634 $this->assertEquals( $post2, $data[0]['id'] ); 635 } 636 637 public function test_get_item() { 638 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 639 $response = $this->server->dispatch( $request ); 640 641 $this->check_get_post_response( $response, 'view' ); 642 } 643 644 public function test_get_item_links() { 645 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 646 $response = $this->server->dispatch( $request ); 647 648 $links = $response->get_links(); 649 650 $this->assertEquals( rest_url( '/wp/v2/posts/' . $this->post_id ), $links['self'][0]['href'] ); 651 $this->assertEquals( rest_url( '/wp/v2/posts' ), $links['collection'][0]['href'] ); 652 653 $this->assertEquals( rest_url( '/wp/v2/types/' . get_post_type( $this->post_id ) ), $links['about'][0]['href'] ); 654 655 $replies_url = rest_url( '/wp/v2/comments' ); 656 $replies_url = add_query_arg( 'post', $this->post_id, $replies_url ); 657 $this->assertEquals( $replies_url, $links['replies'][0]['href'] ); 658 659 $this->assertEquals( rest_url( '/wp/v2/posts/' . $this->post_id . '/revisions' ), $links['version-history'][0]['href'] ); 660 661 $attachments_url = rest_url( '/wp/v2/media' ); 662 $attachments_url = add_query_arg( 'parent', $this->post_id, $attachments_url ); 663 $this->assertEquals( $attachments_url, $links['https://api.w.org/attachment'][0]['href'] ); 664 665 $term_links = $links['https://api.w.org/term']; 666 $tag_link = $cat_link = $format_link = null; 667 foreach ( $term_links as $link ) { 668 if ( 'post_tag' === $link['attributes']['taxonomy'] ) { 669 $tag_link = $link; 670 } elseif ( 'category' === $link['attributes']['taxonomy'] ) { 671 $cat_link = $link; 672 } elseif ( 'post_format' === $link['attributes']['taxonomy'] ) { 673 $format_link = $link; 674 } 675 } 676 $this->assertNotEmpty( $tag_link ); 677 $this->assertNotEmpty( $cat_link ); 678 $this->assertNull( $format_link ); 679 680 $tags_url = add_query_arg( 'post', $this->post_id, rest_url( '/wp/v2/tags' ) ); 681 $this->assertEquals( $tags_url, $tag_link['href'] ); 682 683 $category_url = add_query_arg( 'post', $this->post_id, rest_url( '/wp/v2/categories' ) ); 684 $this->assertEquals( $category_url, $cat_link['href'] ); 685 } 686 687 public function test_get_item_links_no_author() { 688 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 689 $response = $this->server->dispatch( $request ); 690 $links = $response->get_links(); 691 $this->assertFalse( isset( $links['author'] ) ); 692 wp_update_post( array( 'ID' => $this->post_id, 'post_author' => $this->author_id ) ); 693 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 694 $response = $this->server->dispatch( $request ); 695 $links = $response->get_links(); 696 $this->assertEquals( rest_url( '/wp/v2/users/' . $this->author_id ), $links['author'][0]['href'] ); 697 } 698 699 public function test_get_post_without_permission() { 700 $draft_id = $this->factory->post->create( array( 701 'post_status' => 'draft', 702 ) ); 703 wp_set_current_user( 0 ); 704 705 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $draft_id ) ); 706 $response = $this->server->dispatch( $request ); 707 708 $this->assertErrorResponse( 'rest_forbidden', $response, 403 ); 709 } 710 711 public function test_get_post_invalid_id() { 712 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 713 $response = $this->server->dispatch( $request ); 714 715 $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); 716 } 717 718 public function test_get_post_list_context_with_permission() { 719 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 720 $request->set_query_params( array( 721 'context' => 'edit', 722 ) ); 723 724 wp_set_current_user( $this->editor_id ); 725 726 $response = $this->server->dispatch( $request ); 727 728 $this->check_get_posts_response( $response, 'edit' ); 729 } 730 731 public function test_get_post_list_context_without_permission() { 732 wp_set_current_user( 0 ); 733 $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); 734 $request->set_query_params( array( 735 'context' => 'edit', 736 ) ); 737 $response = $this->server->dispatch( $request ); 738 739 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 740 } 741 742 public function test_get_post_context_without_permission() { 743 wp_set_current_user( 0 ); 744 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 745 $request->set_query_params( array( 746 'context' => 'edit', 747 ) ); 748 $response = $this->server->dispatch( $request ); 749 750 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 751 } 752 753 public function test_get_post_with_password() { 754 $post_id = $this->factory->post->create( array( 755 'post_password' => '$inthebananastand', 756 ) ); 757 758 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $post_id ) ); 759 $response = $this->server->dispatch( $request ); 760 761 $this->check_get_post_response( $response, 'view' ); 762 763 $data = $response->get_data(); 764 $this->assertTrue( $data['content']['protected'] ); 765 $this->assertTrue( $data['excerpt']['protected'] ); 766 } 767 768 public function test_get_post_with_password_using_password() { 769 global $wp_version; 770 if ( version_compare( $wp_version, '4.7-alpha', '<' ) ) { 771 return $this->markTestSkipped( 'WordPress < 4.6 does not support filtering passwords for posts.' ); 772 } 773 774 $post_id = $this->factory->post->create( array( 775 'post_password' => '$inthebananastand', 776 'post_content' => 'Some secret content.', 777 'post_excerpt' => 'Some secret excerpt.', 778 ) ); 779 780 $post = get_post( $post_id ); 781 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $post_id ) ); 782 $request->set_param( 'password', '$inthebananastand' ); 783 $response = $this->server->dispatch( $request ); 784 785 $this->check_get_post_response( $response, 'view' ); 786 787 $data = $response->get_data(); 788 $this->assertEquals( wpautop( $post->post_content ), $data['content']['rendered'] ); 789 $this->assertEquals( wpautop( $post->post_excerpt ), $data['excerpt']['rendered'] ); 790 } 791 792 public function test_get_post_with_password_using_incorrect_password() { 793 $post_id = $this->factory->post->create( array( 794 'post_password' => '$inthebananastand', 795 ) ); 796 797 $post = get_post( $post_id ); 798 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $post_id ) ); 799 $request->set_param( 'password', 'wrongpassword' ); 800 $response = $this->server->dispatch( $request ); 801 802 $this->assertErrorResponse( 'rest_post_incorrect_password', $response, 403 ); 803 } 804 805 public function test_get_post_with_password_without_permission() { 806 $post_id = $this->factory->post->create( array( 807 'post_password' => '$inthebananastand', 808 'post_content' => 'Some secret content.', 809 'post_excerpt' => 'Some secret excerpt.', 810 ) ); 811 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $post_id ) ); 812 $response = $this->server->dispatch( $request ); 813 $data = $response->get_data(); 814 $this->check_get_post_response( $response, 'view' ); 815 $this->assertEquals( '', $data['content']['rendered'] ); 816 $this->assertEquals( '', $data['excerpt']['rendered'] ); 817 818 } 819 820 public function test_get_item_read_permission_custom_post_status() { 821 register_post_status( 'testpubstatus', array( 'public' => true ) ); 822 register_post_status( 'testprivtatus', array( 'public' => false ) ); 823 // Public status 824 wp_update_post( array( 'ID' => $this->post_id, 'post_status' => 'testpubstatus' ) ); 825 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 826 $response = $this->server->dispatch( $request ); 827 $this->assertEquals( 200, $response->get_status() ); 828 // Private status 829 wp_update_post( array( 'ID' => $this->post_id, 'post_status' => 'testprivtatus' ) ); 830 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 831 $response = $this->server->dispatch( $request ); 832 $this->assertEquals( 403, $response->get_status() ); 833 } 834 835 public function test_prepare_item() { 836 wp_set_current_user( $this->editor_id ); 837 838 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 839 $request->set_query_params( array( 'context' => 'edit' ) ); 840 $response = $this->server->dispatch( $request ); 841 842 $this->check_get_post_response( $response, 'edit' ); 843 } 844 845 public function test_create_item() { 846 wp_set_current_user( $this->editor_id ); 847 848 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 849 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 850 $params = $this->set_post_data(); 851 $request->set_body_params( $params ); 852 $response = $this->server->dispatch( $request ); 853 854 $this->check_create_post_response( $response ); 855 } 856 857 public function test_rest_create_item() { 858 wp_set_current_user( $this->editor_id ); 859 860 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 861 $request->add_header( 'content-type', 'application/json' ); 862 $params = $this->set_post_data(); 863 $request->set_body( wp_json_encode( $params ) ); 864 $response = $this->server->dispatch( $request ); 865 866 $this->check_create_post_response( $response ); 867 } 868 869 public function test_create_post_invalid_id() { 870 wp_set_current_user( $this->editor_id ); 871 872 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 873 $params = $this->set_post_data( array( 874 'id' => '3', 875 ) ); 876 $request->set_body_params( $params ); 877 $response = $this->server->dispatch( $request ); 878 879 $this->assertErrorResponse( 'rest_post_exists', $response, 400 ); 880 } 881 882 public function test_create_post_as_contributor() { 883 wp_set_current_user( $this->contributor_id ); 884 885 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 886 $params = $this->set_post_data(array( 887 'status' => 'pending', 888 )); 889 890 $request->set_body_params( $params ); 891 $response = $this->server->dispatch( $request ); 892 $this->check_create_post_response( $response ); 893 } 894 895 public function test_create_post_sticky() { 896 wp_set_current_user( $this->editor_id ); 897 898 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 899 $params = $this->set_post_data( array( 900 'sticky' => true, 901 ) ); 902 $request->set_body_params( $params ); 903 $response = $this->server->dispatch( $request ); 904 905 $new_data = $response->get_data(); 906 $this->assertEquals( true, $new_data['sticky'] ); 907 $post = get_post( $new_data['id'] ); 908 $this->assertEquals( true, is_sticky( $post->ID ) ); 909 } 910 911 public function test_create_post_sticky_as_contributor() { 912 wp_set_current_user( $this->contributor_id ); 913 914 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 915 $params = $this->set_post_data( array( 916 'sticky' => true, 917 'status' => 'pending', 918 ) ); 919 $request->set_body_params( $params ); 920 $response = $this->server->dispatch( $request ); 921 922 $this->assertErrorResponse( 'rest_cannot_assign_sticky', $response, 403 ); 923 } 924 925 public function test_create_post_other_author_without_permission() { 926 wp_set_current_user( $this->author_id ); 927 928 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 929 $params = $this->set_post_data(array( 930 'author' => $this->editor_id, 931 )); 932 $request->set_body_params( $params ); 933 $response = $this->server->dispatch( $request ); 934 935 $this->assertErrorResponse( 'rest_cannot_edit_others', $response, 403 ); 936 } 937 938 public function test_create_post_without_permission() { 939 wp_set_current_user( 0 ); 940 941 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 942 $params = $this->set_post_data( array( 943 'status' => 'draft', 944 ) ); 945 $request->set_body_params( $params ); 946 $response = $this->server->dispatch( $request ); 947 948 $this->assertErrorResponse( 'rest_cannot_create', $response, 401 ); 949 } 950 951 public function test_create_post_draft() { 952 wp_set_current_user( $this->editor_id ); 953 954 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 955 $params = $this->set_post_data( array( 956 'status' => 'draft', 957 ) ); 958 $request->set_body_params( $params ); 959 $response = $this->server->dispatch( $request ); 960 961 $data = $response->get_data(); 962 $new_post = get_post( $data['id'] ); 963 $this->assertEquals( 'draft', $data['status'] ); 964 $this->assertEquals( 'draft', $new_post->post_status ); 965 // Confirm dates are null 966 $this->assertNull( $data['date_gmt'] ); 967 $this->assertNull( $data['modified_gmt'] ); 968 } 969 970 public function test_create_post_private() { 971 wp_set_current_user( $this->editor_id ); 972 973 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 974 $params = $this->set_post_data( array( 975 'status' => 'private', 976 ) ); 977 $request->set_body_params( $params ); 978 $response = $this->server->dispatch( $request ); 979 980 $data = $response->get_data(); 981 $new_post = get_post( $data['id'] ); 982 $this->assertEquals( 'private', $data['status'] ); 983 $this->assertEquals( 'private', $new_post->post_status ); 984 } 985 986 public function test_create_post_private_without_permission() { 987 wp_set_current_user( $this->author_id ); 988 $user = wp_get_current_user(); 989 $user->add_cap( 'publish_posts', false ); 990 // Flush capabilities, https://core.trac.wordpress.org/ticket/28374 991 $user->get_role_caps(); 992 $user->update_user_level_from_caps(); 993 994 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 995 $params = $this->set_post_data( array( 996 'status' => 'private', 997 'author' => $this->author_id, 998 ) ); 999 $request->set_body_params( $params ); 1000 $response = $this->server->dispatch( $request ); 1001 1002 $this->assertErrorResponse( 'rest_cannot_publish', $response, 403 ); 1003 } 1004 1005 public function test_create_post_publish_without_permission() { 1006 wp_set_current_user( $this->author_id ); 1007 $user = wp_get_current_user(); 1008 $user->add_cap( 'publish_posts', false ); 1009 // Flush capabilities, https://core.trac.wordpress.org/ticket/28374 1010 $user->get_role_caps(); 1011 $user->update_user_level_from_caps(); 1012 1013 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1014 $params = $this->set_post_data( array( 1015 'status' => 'publish', 1016 ) ); 1017 $request->set_body_params( $params ); 1018 $response = $this->server->dispatch( $request ); 1019 1020 $this->assertErrorResponse( 'rest_cannot_publish', $response, 403 ); 1021 } 1022 1023 public function test_create_post_invalid_status() { 1024 wp_set_current_user( $this->editor_id ); 1025 1026 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1027 $params = $this->set_post_data( array( 1028 'status' => 'teststatus', 1029 ) ); 1030 $request->set_body_params( $params ); 1031 $response = $this->server->dispatch( $request ); 1032 1033 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1034 } 1035 1036 public function test_create_post_with_format() { 1037 wp_set_current_user( $this->editor_id ); 1038 1039 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1040 $params = $this->set_post_data( array( 1041 'format' => 'gallery', 1042 ) ); 1043 $request->set_body_params( $params ); 1044 $response = $this->server->dispatch( $request ); 1045 1046 $data = $response->get_data(); 1047 $new_post = get_post( $data['id'] ); 1048 $this->assertEquals( 'gallery', $data['format'] ); 1049 $this->assertEquals( 'gallery', get_post_format( $new_post->ID ) ); 1050 } 1051 1052 public function test_create_post_with_invalid_format() { 1053 wp_set_current_user( $this->editor_id ); 1054 1055 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1056 $params = $this->set_post_data( array( 1057 'format' => 'testformat', 1058 ) ); 1059 $request->set_body_params( $params ); 1060 $response = $this->server->dispatch( $request ); 1061 1062 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1063 } 1064 1065 public function test_create_update_post_with_featured_media() { 1066 1067 $file = DIR_TESTDATA . '/images/canola.jpg'; 1068 $this->attachment_id = $this->factory->attachment->create_object( $file, 0, array( 1069 'post_mime_type' => 'image/jpeg', 1070 'menu_order' => rand( 1, 100 ), 1071 ) ); 1072 1073 wp_set_current_user( $this->editor_id ); 1074 1075 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1076 $params = $this->set_post_data( array( 1077 'featured_media' => $this->attachment_id, 1078 ) ); 1079 $request->set_body_params( $params ); 1080 $response = $this->server->dispatch( $request ); 1081 $data = $response->get_data(); 1082 $new_post = get_post( $data['id'] ); 1083 $this->assertEquals( $this->attachment_id, $data['featured_media'] ); 1084 $this->assertEquals( $this->attachment_id, (int) get_post_thumbnail_id( $new_post->ID ) ); 1085 1086 $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . $new_post->ID ); 1087 $params = $this->set_post_data( array( 1088 'featured_media' => 0, 1089 ) ); 1090 $request->set_body_params( $params ); 1091 $response = $this->server->dispatch( $request ); 1092 $data = $response->get_data(); 1093 $this->assertEquals( 0, $data['featured_media'] ); 1094 $this->assertEquals( 0, (int) get_post_thumbnail_id( $new_post->ID ) ); 1095 } 1096 1097 public function test_create_post_invalid_author() { 1098 wp_set_current_user( $this->editor_id ); 1099 1100 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1101 $params = $this->set_post_data( array( 1102 'author' => -1, 1103 ) ); 1104 $request->set_body_params( $params ); 1105 $response = $this->server->dispatch( $request ); 1106 1107 $this->assertErrorResponse( 'rest_invalid_author', $response, 400 ); 1108 } 1109 1110 public function test_create_post_invalid_author_without_permission() { 1111 wp_set_current_user( $this->author_id ); 1112 1113 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1114 $params = $this->set_post_data( array( 1115 'author' => $this->editor_id, 1116 ) ); 1117 $request->set_body_params( $params ); 1118 $response = $this->server->dispatch( $request ); 1119 1120 $this->assertErrorResponse( 'rest_cannot_edit_others', $response, 403 ); 1121 } 1122 1123 public function test_create_post_with_password() { 1124 wp_set_current_user( $this->editor_id ); 1125 1126 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1127 $params = $this->set_post_data( array( 1128 'password' => 'testing', 1129 ) ); 1130 $request->set_body_params( $params ); 1131 $response = $this->server->dispatch( $request ); 1132 1133 $data = $response->get_data(); 1134 $this->assertEquals( 'testing', $data['password'] ); 1135 } 1136 1137 public function test_create_post_with_falsy_password() { 1138 wp_set_current_user( $this->editor_id ); 1139 1140 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1141 $params = $this->set_post_data( array( 1142 'password' => '0', 1143 ) ); 1144 $request->set_body_params( $params ); 1145 $response = $this->server->dispatch( $request ); 1146 1147 $data = $response->get_data(); 1148 1149 $this->assertEquals( '0', $data['password'] ); 1150 } 1151 1152 public function test_create_post_with_empty_string_password_and_sticky() { 1153 wp_set_current_user( $this->editor_id ); 1154 1155 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1156 $params = $this->set_post_data( array( 1157 'password' => '', 1158 'sticky' => true, 1159 ) ); 1160 $request->set_body_params( $params ); 1161 $response = $this->server->dispatch( $request ); 1162 1163 $this->assertEquals( 201, $response->get_status() ); 1164 $data = $response->get_data(); 1165 $this->assertEquals( '', $data['password'] ); 1166 } 1167 1168 public function test_create_post_with_password_and_sticky_fails() { 1169 wp_set_current_user( $this->editor_id ); 1170 1171 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1172 $params = $this->set_post_data( array( 1173 'password' => '123', 1174 'sticky' => true, 1175 ) ); 1176 $request->set_body_params( $params ); 1177 $response = $this->server->dispatch( $request ); 1178 1179 $this->assertErrorResponse( 'rest_invalid_field', $response, 400 ); 1180 } 1181 1182 public function test_create_post_custom_date() { 1183 wp_set_current_user( $this->editor_id ); 1184 1185 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1186 $params = $this->set_post_data( array( 1187 'date' => '2010-01-01T02:00:00Z', 1188 ) ); 1189 $request->set_body_params( $params ); 1190 $response = $this->server->dispatch( $request ); 1191 1192 $data = $response->get_data(); 1193 $new_post = get_post( $data['id'] ); 1194 $time = gmmktime( 2, 0, 0, 1, 1, 2010 ); 1195 $this->assertEquals( '2010-01-01T02:00:00', $data['date'] ); 1196 $this->assertEquals( $time, strtotime( $new_post->post_date ) ); 1197 } 1198 1199 public function test_create_post_custom_date_with_timezone() { 1200 wp_set_current_user( $this->editor_id ); 1201 1202 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1203 $params = $this->set_post_data( array( 1204 'date' => '2010-01-01T02:00:00-10:00', 1205 ) ); 1206 $request->set_body_params( $params ); 1207 $response = $this->server->dispatch( $request ); 1208 1209 $data = $response->get_data(); 1210 $new_post = get_post( $data['id'] ); 1211 $time = gmmktime( 12, 0, 0, 1, 1, 2010 ); 1212 1213 $this->assertEquals( '2010-01-01T12:00:00', $data['date'] ); 1214 $this->assertEquals( '2010-01-01T12:00:00', $data['modified'] ); 1215 1216 $this->assertEquals( $time, strtotime( $new_post->post_date ) ); 1217 $this->assertEquals( $time, strtotime( $new_post->post_modified ) ); 1218 } 1219 1220 public function test_create_post_with_db_error() { 1221 wp_set_current_user( $this->editor_id ); 1222 1223 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1224 $params = $this->set_post_data( array() ); 1225 $request->set_body_params( $params ); 1226 1227 /** 1228 * Disable showing error as the below is going to intentionally 1229 * trigger a DB error. 1230 */ 1231 global $wpdb; 1232 $wpdb->suppress_errors = true; 1233 add_filter( 'query', array( $this, 'error_insert_query' ) ); 1234 1235 $response = $this->server->dispatch( $request ); 1236 remove_filter( 'query', array( $this, 'error_insert_query' ) ); 1237 $wpdb->show_errors = true; 1238 1239 $this->assertErrorResponse( 'db_insert_error', $response, 500 ); 1240 } 1241 1242 public function test_create_post_with_invalid_date() { 1243 wp_set_current_user( $this->editor_id ); 1244 1245 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1246 $params = $this->set_post_data( array( 1247 'date' => '2010-60-01T02:00:00Z', 1248 ) ); 1249 $request->set_body_params( $params ); 1250 $response = $this->server->dispatch( $request ); 1251 1252 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1253 } 1254 1255 public function test_create_post_with_invalid_date_gmt() { 1256 wp_set_current_user( $this->editor_id ); 1257 1258 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1259 $params = $this->set_post_data( array( 1260 'date_gmt' => '2010-60-01T02:00:00', 1261 ) ); 1262 $request->set_body_params( $params ); 1263 $response = $this->server->dispatch( $request ); 1264 1265 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1266 } 1267 1268 public function test_create_post_with_quotes_in_title() { 1269 wp_set_current_user( $this->editor_id ); 1270 1271 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1272 $params = $this->set_post_data( array( 1273 'title' => "Rob O'Rourke's Diary", 1274 ) ); 1275 $request->set_body_params( $params ); 1276 $response = $this->server->dispatch( $request ); 1277 $new_data = $response->get_data(); 1278 $this->assertEquals( "Rob O'Rourke's Diary", $new_data['title']['raw'] ); 1279 } 1280 1281 public function test_create_post_with_categories() { 1282 wp_set_current_user( $this->editor_id ); 1283 $category = wp_insert_term( 'Test Category', 'category' ); 1284 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1285 $params = $this->set_post_data( array( 1286 'password' => 'testing', 1287 'categories' => array( 1288 $category['term_id'] 1289 ), 1290 ) ); 1291 $request->set_body_params( $params ); 1292 $response = $this->server->dispatch( $request ); 1293 1294 $data = $response->get_data(); 1295 $this->assertEquals( array( $category['term_id'] ), $data['categories'] ); 1296 } 1297 1298 public function test_create_post_with_invalid_categories() { 1299 wp_set_current_user( $this->editor_id ); 1300 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1301 $params = $this->set_post_data( array( 1302 'password' => 'testing', 1303 'categories' => array( 1304 REST_TESTS_IMPOSSIBLY_HIGH_NUMBER 1305 ), 1306 ) ); 1307 $request->set_body_params( $params ); 1308 $response = $this->server->dispatch( $request ); 1309 1310 $data = $response->get_data(); 1311 $this->assertEquals( array(), $data['categories'] ); 1312 } 1313 1314 public function test_update_item() { 1315 wp_set_current_user( $this->editor_id ); 1316 1317 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1318 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 1319 $params = $this->set_post_data(); 1320 $request->set_body_params( $params ); 1321 $response = $this->server->dispatch( $request ); 1322 1323 $this->check_update_post_response( $response ); 1324 $new_data = $response->get_data(); 1325 $this->assertEquals( $this->post_id, $new_data['id'] ); 1326 $this->assertEquals( $params['title'], $new_data['title']['raw'] ); 1327 $this->assertEquals( $params['content'], $new_data['content']['raw'] ); 1328 $this->assertEquals( $params['excerpt'], $new_data['excerpt']['raw'] ); 1329 $post = get_post( $this->post_id ); 1330 $this->assertEquals( $params['title'], $post->post_title ); 1331 $this->assertEquals( $params['content'], $post->post_content ); 1332 $this->assertEquals( $params['excerpt'], $post->post_excerpt ); 1333 } 1334 1335 public function test_rest_update_post() { 1336 wp_set_current_user( $this->editor_id ); 1337 1338 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1339 $request->add_header( 'content-type', 'application/json' ); 1340 $params = $this->set_post_data(); 1341 $request->set_body( wp_json_encode( $params ) ); 1342 $response = $this->server->dispatch( $request ); 1343 1344 $this->check_update_post_response( $response ); 1345 $new_data = $response->get_data(); 1346 $this->assertEquals( $this->post_id, $new_data['id'] ); 1347 $this->assertEquals( $params['title'], $new_data['title']['raw'] ); 1348 $this->assertEquals( $params['content'], $new_data['content']['raw'] ); 1349 $this->assertEquals( $params['excerpt'], $new_data['excerpt']['raw'] ); 1350 $post = get_post( $this->post_id ); 1351 $this->assertEquals( $params['title'], $post->post_title ); 1352 $this->assertEquals( $params['content'], $post->post_content ); 1353 $this->assertEquals( $params['excerpt'], $post->post_excerpt ); 1354 } 1355 1356 public function test_rest_update_post_raw() { 1357 wp_set_current_user( $this->editor_id ); 1358 1359 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1360 $request->add_header( 'content-type', 'application/json' ); 1361 $params = $this->set_raw_post_data(); 1362 $request->set_body( wp_json_encode( $params ) ); 1363 $response = $this->server->dispatch( $request ); 1364 1365 $this->check_update_post_response( $response ); 1366 $new_data = $response->get_data(); 1367 $this->assertEquals( $this->post_id, $new_data['id'] ); 1368 $this->assertEquals( $params['title']['raw'], $new_data['title']['raw'] ); 1369 $this->assertEquals( $params['content']['raw'], $new_data['content']['raw'] ); 1370 $this->assertEquals( $params['excerpt']['raw'], $new_data['excerpt']['raw'] ); 1371 $post = get_post( $this->post_id ); 1372 $this->assertEquals( $params['title']['raw'], $post->post_title ); 1373 $this->assertEquals( $params['content']['raw'], $post->post_content ); 1374 $this->assertEquals( $params['excerpt']['raw'], $post->post_excerpt ); 1375 } 1376 1377 public function test_update_post_without_extra_params() { 1378 wp_set_current_user( $this->editor_id ); 1379 1380 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1381 $params = $this->set_post_data(); 1382 unset( $params['type'] ); 1383 unset( $params['name'] ); 1384 unset( $params['author'] ); 1385 unset( $params['status'] ); 1386 $request->set_body_params( $params ); 1387 $response = $this->server->dispatch( $request ); 1388 1389 $this->check_update_post_response( $response ); 1390 } 1391 1392 public function test_update_post_without_permission() { 1393 wp_set_current_user( $this->editor_id ); 1394 $user = wp_get_current_user(); 1395 $user->add_cap( 'edit_published_posts', false ); 1396 // Flush capabilities, https://core.trac.wordpress.org/ticket/28374 1397 $user->get_role_caps(); 1398 $user->update_user_level_from_caps(); 1399 1400 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1401 $params = $this->set_post_data(); 1402 $request->set_body_params( $params ); 1403 $response = $this->server->dispatch( $request ); 1404 1405 $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); 1406 } 1407 1408 public function test_update_post_sticky_as_contributor() { 1409 wp_set_current_user( $this->contributor_id ); 1410 1411 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1412 $params = $this->set_post_data( array( 1413 'sticky' => true, 1414 'status' => 'pending', 1415 ) ); 1416 $request->set_body_params( $params ); 1417 $response = $this->server->dispatch( $request ); 1418 1419 $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); 1420 } 1421 1422 public function test_update_post_invalid_id() { 1423 wp_set_current_user( $this->editor_id ); 1424 1425 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ) ); 1426 $response = $this->server->dispatch( $request ); 1427 1428 $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); 1429 } 1430 1431 public function test_update_post_invalid_route() { 1432 wp_set_current_user( $this->editor_id ); 1433 1434 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/pages/%d', $this->post_id ) ); 1435 $response = $this->server->dispatch( $request ); 1436 1437 $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); 1438 } 1439 1440 public function test_update_post_with_format() { 1441 wp_set_current_user( $this->editor_id ); 1442 1443 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1444 $params = $this->set_post_data( array( 1445 'format' => 'gallery', 1446 ) ); 1447 $request->set_body_params( $params ); 1448 $response = $this->server->dispatch( $request ); 1449 1450 $data = $response->get_data(); 1451 $new_post = get_post( $data['id'] ); 1452 $this->assertEquals( 'gallery', $data['format'] ); 1453 $this->assertEquals( 'gallery', get_post_format( $new_post->ID ) ); 1454 } 1455 1456 public function test_update_post_with_invalid_format() { 1457 wp_set_current_user( $this->editor_id ); 1458 1459 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1460 $params = $this->set_post_data( array( 1461 'format' => 'testformat', 1462 ) ); 1463 $request->set_body_params( $params ); 1464 $response = $this->server->dispatch( $request ); 1465 1466 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1467 } 1468 1469 public function test_update_post_ignore_readonly() { 1470 wp_set_current_user( $this->editor_id ); 1471 1472 $new_content = rand_str(); 1473 $expected_modified = current_time( 'mysql' ); 1474 1475 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1476 $params = $this->set_post_data( array( 1477 'modified' => '2010-06-01T02:00:00Z', 1478 'content' => $new_content, 1479 ) ); 1480 $request->set_body_params( $params ); 1481 $response = $this->server->dispatch( $request ); 1482 1483 // The readonly modified param should be ignored, request should be a success. 1484 $data = $response->get_data(); 1485 $new_post = get_post( $data['id'] ); 1486 1487 $this->assertEquals( $new_content, $data['content']['raw'] ); 1488 $this->assertEquals( $new_content, $new_post->post_content ); 1489 1490 // The modified date should equal the current time. 1491 $this->assertEquals( date( 'Y-m-d', strtotime( mysql_to_rfc3339( $expected_modified ) ) ), date( 'Y-m-d', strtotime( $data['modified'] ) ) ); 1492 $this->assertEquals( date( 'Y-m-d', strtotime( $expected_modified ) ), date( 'Y-m-d', strtotime( $new_post->post_modified ) ) ); 1493 } 1494 1495 public function test_update_post_with_invalid_date() { 1496 wp_set_current_user( $this->editor_id ); 1497 1498 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1499 $params = $this->set_post_data( array( 1500 'date' => rand_str(), 1501 ) ); 1502 $request->set_body_params( $params ); 1503 $response = $this->server->dispatch( $request ); 1504 1505 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1506 } 1507 1508 public function test_update_post_with_invalid_date_gmt() { 1509 wp_set_current_user( $this->editor_id ); 1510 1511 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1512 $params = $this->set_post_data( array( 1513 'date_gmt' => rand_str(), 1514 ) ); 1515 $request->set_body_params( $params ); 1516 $response = $this->server->dispatch( $request ); 1517 1518 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1519 } 1520 1521 public function test_update_post_slug() { 1522 wp_set_current_user( $this->editor_id ); 1523 1524 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1525 $params = $this->set_post_data( array( 1526 'slug' => 'sample-slug', 1527 ) ); 1528 $request->set_body_params( $params ); 1529 $response = $this->server->dispatch( $request ); 1530 1531 $new_data = $response->get_data(); 1532 $this->assertEquals( 'sample-slug', $new_data['slug'] ); 1533 $post = get_post( $new_data['id'] ); 1534 $this->assertEquals( 'sample-slug', $post->post_name ); 1535 } 1536 1537 public function test_update_post_slug_accented_chars() { 1538 wp_set_current_user( $this->editor_id ); 1539 1540 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1541 $params = $this->set_post_data( array( 1542 'slug' => 'tęst-acceńted-chäræcters', 1543 ) ); 1544 $request->set_body_params( $params ); 1545 $response = $this->server->dispatch( $request ); 1546 1547 $new_data = $response->get_data(); 1548 $this->assertEquals( 'test-accented-charaecters', $new_data['slug'] ); 1549 $post = get_post( $new_data['id'] ); 1550 $this->assertEquals( 'test-accented-charaecters', $post->post_name ); 1551 } 1552 1553 public function test_update_post_sticky() { 1554 wp_set_current_user( $this->editor_id ); 1555 1556 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1557 $params = $this->set_post_data( array( 1558 'sticky' => true, 1559 ) ); 1560 $request->set_body_params( $params ); 1561 $response = $this->server->dispatch( $request ); 1562 1563 $new_data = $response->get_data(); 1564 $this->assertEquals( true, $new_data['sticky'] ); 1565 $post = get_post( $new_data['id'] ); 1566 $this->assertEquals( true, is_sticky( $post->ID ) ); 1567 1568 // Updating another field shouldn't change sticky status 1569 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1570 $params = $this->set_post_data( array( 1571 'title' => 'This should not reset sticky', 1572 ) ); 1573 $request->set_body_params( $params ); 1574 $response = $this->server->dispatch( $request ); 1575 1576 $new_data = $response->get_data(); 1577 $this->assertEquals( true, $new_data['sticky'] ); 1578 $post = get_post( $new_data['id'] ); 1579 $this->assertEquals( true, is_sticky( $post->ID ) ); 1580 } 1581 1582 public function test_update_post_excerpt() { 1583 wp_set_current_user( $this->editor_id ); 1584 1585 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1586 $request->set_body_params( array( 1587 'excerpt' => 'An Excerpt', 1588 ) ); 1589 1590 $response = $this->server->dispatch( $request ); 1591 $new_data = $response->get_data(); 1592 $this->assertEquals( 'An Excerpt', $new_data['excerpt']['raw'] ); 1593 } 1594 1595 public function test_update_post_empty_excerpt() { 1596 wp_set_current_user( $this->editor_id ); 1597 1598 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1599 $request->set_body_params( array( 1600 'excerpt' => '', 1601 ) ); 1602 1603 $response = $this->server->dispatch( $request ); 1604 $new_data = $response->get_data(); 1605 $this->assertEquals( '', $new_data['excerpt']['raw'] ); 1606 } 1607 1608 public function test_update_post_content() { 1609 wp_set_current_user( $this->editor_id ); 1610 1611 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1612 $request->set_body_params( array( 1613 'content' => 'Some Content', 1614 ) ); 1615 1616 $response = $this->server->dispatch( $request ); 1617 $new_data = $response->get_data(); 1618 $this->assertEquals( 'Some Content', $new_data['content']['raw'] ); 1619 } 1620 1621 public function test_update_post_empty_content() { 1622 wp_set_current_user( $this->editor_id ); 1623 1624 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1625 $request->set_body_params( array( 1626 'content' => '', 1627 ) ); 1628 1629 $response = $this->server->dispatch( $request ); 1630 $new_data = $response->get_data(); 1631 $this->assertEquals( '', $new_data['content']['raw'] ); 1632 } 1633 1634 public function test_update_post_with_password_and_sticky_fails() { 1635 wp_set_current_user( $this->editor_id ); 1636 1637 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1638 $params = $this->set_post_data( array( 1639 'password' => '123', 1640 'sticky' => true, 1641 ) ); 1642 $request->set_body_params( $params ); 1643 $response = $this->server->dispatch( $request ); 1644 1645 $this->assertErrorResponse( 'rest_invalid_field', $response, 400 ); 1646 } 1647 1648 public function test_update_stick_post_with_password_fails() { 1649 wp_set_current_user( $this->editor_id ); 1650 1651 stick_post( $this->post_id ); 1652 1653 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1654 $params = $this->set_post_data( array( 1655 'password' => '123', 1656 ) ); 1657 $request->set_body_params( $params ); 1658 $response = $this->server->dispatch( $request ); 1659 1660 $this->assertErrorResponse( 'rest_invalid_field', $response, 400 ); 1661 } 1662 1663 public function test_update_password_protected_post_with_sticky_fails() { 1664 wp_set_current_user( $this->editor_id ); 1665 1666 wp_update_post( array( 'ID' => $this->post_id, 'post_password' => '123' ) ); 1667 1668 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1669 $params = $this->set_post_data( array( 1670 'sticky' => true, 1671 ) ); 1672 $request->set_body_params( $params ); 1673 $response = $this->server->dispatch( $request ); 1674 1675 $this->assertErrorResponse( 'rest_invalid_field', $response, 400 ); 1676 } 1677 1678 public function test_update_post_with_quotes_in_title() { 1679 wp_set_current_user( $this->editor_id ); 1680 1681 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1682 $params = $this->set_post_data( array( 1683 'title' => "Rob O'Rourke's Diary", 1684 ) ); 1685 $request->set_body_params( $params ); 1686 $response = $this->server->dispatch( $request ); 1687 $new_data = $response->get_data(); 1688 $this->assertEquals( "Rob O'Rourke's Diary", $new_data['title']['raw'] ); 1689 } 1690 1691 public function test_update_post_with_categories() { 1692 1693 wp_set_current_user( $this->editor_id ); 1694 $category = wp_insert_term( 'Test Category', 'category' ); 1695 1696 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1697 $params = $this->set_post_data( array( 1698 'title' => 'Tester', 1699 'categories' => array( 1700 $category['term_id'], 1701 ), 1702 ) ); 1703 $request->set_body_params( $params ); 1704 $response = $this->server->dispatch( $request ); 1705 $new_data = $response->get_data(); 1706 $this->assertEquals( array( $category['term_id'] ), $new_data['categories'] ); 1707 $categories_path = ''; 1708 $links = $response->get_links(); 1709 foreach ( $links['https://api.w.org/term'] as $link ) { 1710 if ( 'category' === $link['attributes']['taxonomy'] ) { 1711 $categories_path = $link['href']; 1712 } 1713 } 1714 $query = parse_url( $categories_path, PHP_URL_QUERY ); 1715 parse_str( $query, $args ); 1716 $request = new WP_REST_Request( 'GET', $args['rest_route'] ); 1717 unset( $args['rest_route'] ); 1718 $request->set_query_params( $args ); 1719 $response = $this->server->dispatch( $request ); 1720 $data = $response->get_data(); 1721 $this->assertCount( 1, $data ); 1722 $this->assertEquals( 'Test Category', $data[0]['name'] ); 1723 } 1724 1725 public function test_update_post_with_empty_categories() { 1726 1727 wp_set_current_user( $this->editor_id ); 1728 $category = wp_insert_term( 'Test Category', 'category' ); 1729 wp_set_object_terms( $this->post_id, $category['term_id'], 'category' ); 1730 1731 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1732 $params = $this->set_post_data( array( 1733 'title' => 'Tester', 1734 'categories' => array(), 1735 ) ); 1736 $request->set_body_params( $params ); 1737 $response = $this->server->dispatch( $request ); 1738 $new_data = $response->get_data(); 1739 $this->assertEquals( array(), $new_data['categories'] ); 1740 } 1741 1742 public function test_delete_item() { 1743 $post_id = $this->factory->post->create( array( 'post_title' => 'Deleted post' ) ); 1744 wp_set_current_user( $this->editor_id ); 1745 1746 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/posts/%d', $post_id ) ); 1747 $response = $this->server->dispatch( $request ); 1748 1749 $this->assertNotInstanceOf( 'WP_Error', $response ); 1750 $this->assertEquals( 200, $response->get_status() ); 1751 $data = $response->get_data(); 1752 $this->assertEquals( 'Deleted post', $data['title']['raw'] ); 1753 } 1754 1755 public function test_delete_item_skip_trash() { 1756 $post_id = $this->factory->post->create( array( 'post_title' => 'Deleted post' ) ); 1757 wp_set_current_user( $this->editor_id ); 1758 1759 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/posts/%d', $post_id ) ); 1760 $request['force'] = true; 1761 $response = $this->server->dispatch( $request ); 1762 1763 $this->assertNotInstanceOf( 'WP_Error', $response ); 1764 $this->assertEquals( 200, $response->get_status() ); 1765 $data = $response->get_data(); 1766 $this->assertEquals( 'Deleted post', $data['title']['raw'] ); 1767 } 1768 1769 public function test_delete_item_already_trashed() { 1770 $post_id = $this->factory->post->create( array( 'post_title' => 'Deleted post' ) ); 1771 wp_set_current_user( $this->editor_id ); 1772 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/posts/%d', $post_id ) ); 1773 $response = $this->server->dispatch( $request ); 1774 $this->assertEquals( 200, $response->get_status() ); 1775 $response = $this->server->dispatch( $request ); 1776 $this->assertErrorResponse( 'rest_already_trashed', $response, 410 ); 1777 } 1778 1779 public function test_delete_post_invalid_id() { 1780 wp_set_current_user( $this->editor_id ); 1781 1782 $request = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 1783 $response = $this->server->dispatch( $request ); 1784 1785 $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); 1786 } 1787 1788 public function test_delete_post_invalid_post_type() { 1789 $page_id = $this->factory->post->create( array( 'post_type' => 'page' ) ); 1790 wp_set_current_user( $this->editor_id ); 1791 1792 $request = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . $page_id ); 1793 $response = $this->server->dispatch( $request ); 1794 1795 $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); 1796 } 1797 1798 public function test_delete_post_without_permission() { 1799 wp_set_current_user( $this->author_id ); 1800 1801 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1802 $response = $this->server->dispatch( $request ); 1803 1804 $this->assertErrorResponse( 'rest_cannot_delete', $response, 403 ); 1805 } 1806 1807 public function test_register_post_type_invalid_controller() { 1808 1809 register_post_type( 'invalid-controller', array( 'show_in_rest' => true, 'rest_controller_class' => 'Fake_Class_Baba' ) ); 1810 create_initial_rest_routes(); 1811 $routes = $this->server->get_routes(); 1812 $this->assertFalse( isset( $routes['/wp/v2/invalid-controller'] ) ); 1813 _unregister_post_type( 'invalid-controller' ); 1814 1815 } 1816 1817 public function test_get_item_schema() { 1818 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts' ); 1819 $response = $this->server->dispatch( $request ); 1820 $data = $response->get_data(); 1821 $properties = $data['schema']['properties']; 1822 $this->assertEquals( 25, count( $properties ) ); 1823 $this->assertArrayHasKey( 'author', $properties ); 1824 $this->assertArrayHasKey( 'comment_status', $properties ); 1825 $this->assertArrayHasKey( 'content', $properties ); 1826 $this->assertArrayHasKey( 'date', $properties ); 1827 $this->assertArrayHasKey( 'date_gmt', $properties ); 1828 $this->assertArrayHasKey( 'excerpt', $properties ); 1829 $this->assertArrayHasKey( 'featured_media', $properties ); 1830 $this->assertArrayHasKey( 'guid', $properties ); 1831 $this->assertArrayHasKey( 'format', $properties ); 1832 $this->assertArrayHasKey( 'id', $properties ); 1833 $this->assertArrayHasKey( 'link', $properties ); 1834 $this->assertArrayHasKey( 'meta', $properties ); 1835 $this->assertArrayHasKey( 'modified', $properties ); 1836 $this->assertArrayHasKey( 'modified_gmt', $properties ); 1837 $this->assertArrayHasKey( 'password', $properties ); 1838 $this->assertArrayHasKey( 'ping_status', $properties ); 1839 $this->assertArrayHasKey( 'slug', $properties ); 1840 $this->assertArrayHasKey( 'status', $properties ); 1841 $this->assertArrayHasKey( 'sticky', $properties ); 1842 $this->assertArrayHasKey( 'title', $properties ); 1843 $this->assertArrayHasKey( 'type', $properties ); 1844 $this->assertArrayHasKey( 'tags', $properties ); 1845 $this->assertArrayHasKey( 'tags_exclude', $properties ); 1846 $this->assertArrayHasKey( 'categories', $properties ); 1847 $this->assertArrayHasKey( 'categories_exclude', $properties ); 1848 } 1849 1850 public function test_get_additional_field_registration() { 1851 1852 $schema = array( 1853 'type' => 'integer', 1854 'description' => 'Some integer of mine', 1855 'enum' => array( 1, 2, 3, 4 ), 1856 'context' => array( 'view', 'edit' ), 1857 ); 1858 1859 register_rest_field( 'post', 'my_custom_int', array( 1860 'schema' => $schema, 1861 'get_callback' => array( $this, 'additional_field_get_callback' ), 1862 'update_callback' => array( $this, 'additional_field_update_callback' ), 1863 ) ); 1864 1865 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts' ); 1866 1867 $response = $this->server->dispatch( $request ); 1868 $data = $response->get_data(); 1869 1870 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 1871 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 1872 1873 wp_set_current_user( 1 ); 1874 1875 $post_id = $this->factory->post->create(); 1876 1877 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); 1878 1879 $response = $this->server->dispatch( $request ); 1880 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 1881 1882 $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . $post_id ); 1883 $request->set_body_params(array( 1884 'my_custom_int' => 123, 1885 )); 1886 1887 $response = $this->server->dispatch( $request ); 1888 $this->assertEquals( 123, get_post_meta( $post_id, 'my_custom_int', true ) ); 1889 1890 $request = new WP_REST_Request( 'POST', '/wp/v2/posts' ); 1891 $request->set_body_params(array( 1892 'my_custom_int' => 123, 1893 'title' => 'hello', 1894 )); 1895 1896 $response = $this->server->dispatch( $request ); 1897 1898 $this->assertEquals( 123, $response->data['my_custom_int'] ); 1899 1900 global $wp_rest_additional_fields; 1901 $wp_rest_additional_fields = array(); 1902 } 1903 1904 public function test_additional_field_update_errors() { 1905 $schema = array( 1906 'type' => 'integer', 1907 'description' => 'Some integer of mine', 1908 'enum' => array( 1, 2, 3, 4 ), 1909 'context' => array( 'view', 'edit' ), 1910 ); 1911 1912 register_rest_field( 'post', 'my_custom_int', array( 1913 'schema' => $schema, 1914 'get_callback' => array( $this, 'additional_field_get_callback' ), 1915 'update_callback' => array( $this, 'additional_field_update_callback' ), 1916 ) ); 1917 1918 wp_set_current_user( $this->editor_id ); 1919 // Check for error on update. 1920 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) ); 1921 $request->set_body_params( array( 1922 'my_custom_int' => 'returnError', 1923 ) ); 1924 1925 $response = $this->server->dispatch( $request ); 1926 1927 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1928 1929 global $wp_rest_additional_fields; 1930 $wp_rest_additional_fields = array(); 1931 } 1932 1933 public function additional_field_get_callback( $object ) { 1934 return get_post_meta( $object['id'], 'my_custom_int', true ); 1935 } 1936 1937 public function additional_field_update_callback( $value, $post ) { 1938 if ( 'returnError' === $value ) { 1939 return new WP_Error( 'rest_invalid_param', 'Testing an error.', array( 'status' => 400 ) ); 1940 } 1941 update_post_meta( $post->ID, 'my_custom_int', $value ); 1942 } 1943 1944 public function tearDown() { 1945 _unregister_post_type( 'youseeeme' ); 1946 if ( isset( $this->attachment_id ) ) { 1947 $this->remove_added_uploads(); 1948 } 1949 parent::tearDown(); 1950 } 1951 1952 /** 1953 * Internal function used to disable an insert query which 1954 * will trigger a wpdb error for testing purposes. 1955 */ 1956 public function error_insert_query( $query ) { 1957 if ( strpos( $query, 'INSERT' ) === 0 ) { 1958 $query = '],'; 1959 } 1960 return $query; 1961 } 1962 1963 } -
tests/phpunit/tests/rest-api/rest-request-validation.php
1 <?php 2 3 class WP_Test_REST_Request_Validation extends WP_Test_REST_TestCase { 4 5 public function test_validate_within_min_max_range_inclusive() { 6 $request = new WP_REST_Request( 'GET', '/wp/v2/foo', array( 7 'args' => array( 8 'minmaxrange' => array( 9 'type' => 'integer', 10 'minimum' => 2, 11 'maximum' => 10, 12 ), 13 ), 14 ) ); 15 $ret = rest_validate_request_arg( 1, $request, 'minmaxrange' ); 16 $this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (inclusive)', $ret->get_error_message() ); 17 $ret = rest_validate_request_arg( 2, $request, 'minmaxrange' ); 18 $this->assertTrue( $ret ); 19 $ret = rest_validate_request_arg( 10, $request, 'minmaxrange' ); 20 $this->assertTrue( $ret ); 21 $ret = rest_validate_request_arg( 11, $request, 'minmaxrange' ); 22 $this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (inclusive)', $ret->get_error_message() ); 23 } 24 25 public function test_validate_within_min_max_range_min_exclusive() { 26 $request = new WP_REST_Request( 'GET', '/wp/v2/foo', array( 27 'args' => array( 28 'minmaxrange' => array( 29 'type' => 'integer', 30 'minimum' => 2, 31 'maximum' => 10, 32 'exclusiveMinimum' => true, 33 ), 34 ), 35 ) ); 36 $ret = rest_validate_request_arg( 1, $request, 'minmaxrange' ); 37 $this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (inclusive)', $ret->get_error_message() ); 38 $ret = rest_validate_request_arg( 2, $request, 'minmaxrange' ); 39 $this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (inclusive)', $ret->get_error_message() ); 40 $ret = rest_validate_request_arg( 3, $request, 'minmaxrange' ); 41 $this->assertTrue( $ret ); 42 $ret = rest_validate_request_arg( 9, $request, 'minmaxrange' ); 43 $this->assertTrue( $ret ); 44 $ret = rest_validate_request_arg( 10, $request, 'minmaxrange' ); 45 $this->assertTrue( $ret ); 46 $ret = rest_validate_request_arg( 11, $request, 'minmaxrange' ); 47 $this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (inclusive)', $ret->get_error_message() ); 48 } 49 50 public function test_validate_within_min_max_range_max_exclusive() { 51 $request = new WP_REST_Request( 'GET', '/wp/v2/foo', array( 52 'args' => array( 53 'minmaxrange' => array( 54 'type' => 'integer', 55 'minimum' => 2, 56 'maximum' => 10, 57 'exclusiveMaximum' => true, 58 ), 59 ), 60 ) ); 61 $ret = rest_validate_request_arg( 1, $request, 'minmaxrange' ); 62 $this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (exclusive)', $ret->get_error_message() ); 63 $ret = rest_validate_request_arg( 2, $request, 'minmaxrange' ); 64 $this->assertTrue( $ret ); 65 $ret = rest_validate_request_arg( 3, $request, 'minmaxrange' ); 66 $this->assertTrue( $ret ); 67 $ret = rest_validate_request_arg( 9, $request, 'minmaxrange' ); 68 $this->assertTrue( $ret ); 69 $ret = rest_validate_request_arg( 10, $request, 'minmaxrange' ); 70 $this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (exclusive)', $ret->get_error_message() ); 71 $ret = rest_validate_request_arg( 11, $request, 'minmaxrange' ); 72 $this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (exclusive)', $ret->get_error_message() ); 73 } 74 75 public function test_validate_within_min_max_range_both_exclusive() { 76 $request = new WP_REST_Request( 'GET', '/wp/v2/foo', array( 77 'args' => array( 78 'minmaxrange' => array( 79 'type' => 'integer', 80 'minimum' => 2, 81 'maximum' => 10, 82 'exclusiveMinimum' => true, 83 'exclusiveMaximum' => true, 84 ), 85 ), 86 ) ); 87 $ret = rest_validate_request_arg( 1, $request, 'minmaxrange' ); 88 $this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (exclusive)', $ret->get_error_message() ); 89 $ret = rest_validate_request_arg( 2, $request, 'minmaxrange' ); 90 $this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (exclusive)', $ret->get_error_message() ); 91 $ret = rest_validate_request_arg( 3, $request, 'minmaxrange' ); 92 $this->assertTrue( $ret ); 93 $ret = rest_validate_request_arg( 9, $request, 'minmaxrange' ); 94 $this->assertTrue( $ret ); 95 $ret = rest_validate_request_arg( 10, $request, 'minmaxrange' ); 96 $this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (exclusive)', $ret->get_error_message() ); 97 $ret = rest_validate_request_arg( 11, $request, 'minmaxrange' ); 98 $this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (exclusive)', $ret->get_error_message() ); 99 } 100 101 public function test_validate_greater_than_min_inclusive() { 102 $request = new WP_REST_Request( 'GET', '/wp/v2/foo', array( 103 'args' => array( 104 'greaterthanmin' => array( 105 'type' => 'integer', 106 'minimum' => 2, 107 ), 108 ), 109 ) ); 110 $ret = rest_validate_request_arg( 1, $request, 'greaterthanmin' ); 111 $this->assertEquals( 'greaterthanmin must be greater than 2 (inclusive)', $ret->get_error_message() ); 112 $ret = rest_validate_request_arg( 2, $request, 'greaterthanmin' ); 113 $this->assertTrue( $ret ); 114 } 115 116 public function test_validate_greater_than_min_exclusive() { 117 $request = new WP_REST_Request( 'GET', '/wp/v2/foo', array( 118 'args' => array( 119 'greaterthanmin' => array( 120 'type' => 'integer', 121 'minimum' => 2, 122 'exclusiveMinimum' => true, 123 ), 124 ), 125 ) ); 126 $ret = rest_validate_request_arg( 1, $request, 'greaterthanmin' ); 127 $this->assertEquals( 'greaterthanmin must be greater than 2 (exclusive)', $ret->get_error_message() ); 128 $ret = rest_validate_request_arg( 2, $request, 'greaterthanmin' ); 129 $this->assertEquals( 'greaterthanmin must be greater than 2 (exclusive)', $ret->get_error_message() ); 130 $ret = rest_validate_request_arg( 3, $request, 'greaterthanmin' ); 131 $this->assertTrue( $ret ); 132 } 133 134 public function test_validate_less_than_max_inclusive() { 135 $request = new WP_REST_Request( 'GET', '/wp/v2/foo', array( 136 'args' => array( 137 'lessthanmax' => array( 138 'type' => 'integer', 139 'maximum' => 10, 140 ), 141 ), 142 ) ); 143 $ret = rest_validate_request_arg( 11, $request, 'lessthanmax' ); 144 $this->assertEquals( 'lessthanmax must be less than 10 (inclusive)', $ret->get_error_message() ); 145 $ret = rest_validate_request_arg( 10, $request, 'lessthanmax' ); 146 $this->assertTrue( $ret ); 147 } 148 149 public function test_validate_less_than_max_exclusive() { 150 $request = new WP_REST_Request( 'GET', '/wp/v2/foo', array( 151 'args' => array( 152 'lessthanmax' => array( 153 'type' => 'integer', 154 'maximum' => 10, 155 'exclusiveMaximum' => true, 156 ), 157 ), 158 ) ); 159 $ret = rest_validate_request_arg( 11, $request, 'lessthanmax' ); 160 $this->assertEquals( 'lessthanmax must be less than 10 (exclusive)', $ret->get_error_message() ); 161 $ret = rest_validate_request_arg( 10, $request, 'lessthanmax' ); 162 $this->assertEquals( 'lessthanmax must be less than 10 (exclusive)', $ret->get_error_message() ); 163 $ret = rest_validate_request_arg( 9, $request, 'lessthanmax' ); 164 $this->assertTrue( $ret ); 165 } 166 167 } -
tests/phpunit/tests/rest-api/rest-revisions-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Revisions_Controller functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase { 13 14 public function setUp() { 15 parent::setUp(); 16 $this->post_id = $this->factory->post->create(); 17 $this->page_id = $this->factory->post->create( array( 'post_type' => 'page' ) ); 18 19 $this->editor_id = $this->factory->user->create( array( 20 'role' => 'editor', 21 ) ); 22 $this->contributor_id = $this->factory->user->create( array( 23 'role' => 'contributor', 24 ) ); 25 26 wp_update_post( array( 'post_content' => 'This content is better.', 'ID' => $this->post_id ) ); 27 wp_update_post( array( 'post_content' => 'This content is marvelous.', 'ID' => $this->post_id ) ); 28 $revisions = wp_get_post_revisions( $this->post_id ); 29 $this->revision_1 = array_pop( $revisions ); 30 $this->revision_id1 = $this->revision_1->ID; 31 $this->revision_2 = array_pop( $revisions ); 32 $this->revision_id2 = $this->revision_2->ID; 33 } 34 35 public function test_register_routes() { 36 $routes = $this->server->get_routes(); 37 $this->assertArrayHasKey( '/wp/v2/posts/(?P<parent>[\d]+)/revisions', $routes ); 38 $this->assertArrayHasKey( '/wp/v2/posts/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)', $routes ); 39 $this->assertArrayHasKey( '/wp/v2/pages/(?P<parent>[\d]+)/revisions', $routes ); 40 $this->assertArrayHasKey( '/wp/v2/pages/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)', $routes ); 41 } 42 43 public function test_context_param() { 44 // Collection 45 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id . '/revisions' ); 46 $response = $this->server->dispatch( $request ); 47 $data = $response->get_data(); 48 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 49 $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); 50 // Single 51 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_1->ID ); 52 $response = $this->server->dispatch( $request ); 53 $data = $response->get_data(); 54 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 55 $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); 56 } 57 58 public function test_get_items() { 59 wp_set_current_user( $this->editor_id ); 60 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions' ); 61 $response = $this->server->dispatch( $request ); 62 $data = $response->get_data(); 63 $this->assertEquals( 200, $response->get_status() ); 64 $this->assertCount( 2, $data ); 65 66 // Reverse chron 67 $this->assertEquals( $this->revision_id2, $data[0]['id'] ); 68 $this->check_get_revision_response( $data[0], $this->revision_2 ); 69 70 $this->assertEquals( $this->revision_id1, $data[1]['id'] ); 71 $this->check_get_revision_response( $data[1], $this->revision_1 ); 72 } 73 74 public function test_get_items_no_permission() { 75 wp_set_current_user( 0 ); 76 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions' ); 77 $response = $this->server->dispatch( $request ); 78 79 $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); 80 wp_set_current_user( $this->contributor_id ); 81 $response = $this->server->dispatch( $request ); 82 $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); 83 } 84 85 public function test_get_items_missing_parent() { 86 wp_set_current_user( $this->editor_id ); 87 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions' ); 88 $response = $this->server->dispatch( $request ); 89 $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); 90 } 91 92 public function test_get_items_invalid_parent_post_type() { 93 wp_set_current_user( $this->editor_id ); 94 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->page_id . '/revisions' ); 95 $response = $this->server->dispatch( $request ); 96 $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); 97 } 98 99 public function test_get_item() { 100 wp_set_current_user( $this->editor_id ); 101 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 ); 102 $response = $this->server->dispatch( $request ); 103 $this->assertEquals( 200, $response->get_status() ); 104 $this->check_get_revision_response( $response, $this->revision_1 ); 105 $fields = array( 106 'author', 107 'date', 108 'date_gmt', 109 'modified', 110 'modified_gmt', 111 'guid', 112 'id', 113 'parent', 114 'slug', 115 'title', 116 'excerpt', 117 'content', 118 ); 119 $data = $response->get_data(); 120 $this->assertEqualSets( $fields, array_keys( $data ) ); 121 } 122 123 public function test_get_item_embed_context() { 124 wp_set_current_user( $this->editor_id ); 125 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 ); 126 $request->set_param( 'context', 'embed' ); 127 $response = $this->server->dispatch( $request ); 128 $fields = array( 129 'author', 130 'date', 131 'id', 132 'parent', 133 'slug', 134 'title', 135 'excerpt', 136 ); 137 $data = $response->get_data(); 138 $this->assertEqualSets( $fields, array_keys( $data ) ); 139 } 140 141 public function test_get_item_no_permission() { 142 wp_set_current_user( 0 ); 143 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 ); 144 145 $response = $this->server->dispatch( $request ); 146 $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); 147 wp_set_current_user( $this->contributor_id ); 148 $response = $this->server->dispatch( $request ); 149 $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); 150 } 151 152 public function test_get_item_missing_parent() { 153 wp_set_current_user( $this->editor_id ); 154 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions/' . $this->revision_id1 ); 155 $response = $this->server->dispatch( $request ); 156 $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); 157 } 158 159 public function test_get_item_invalid_parent_post_type() { 160 wp_set_current_user( $this->editor_id ); 161 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->page_id . '/revisions/' . $this->revision_id1 ); 162 $response = $this->server->dispatch( $request ); 163 $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); 164 } 165 166 public function test_delete_item() { 167 wp_set_current_user( $this->editor_id ); 168 $request = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 ); 169 $response = $this->server->dispatch( $request ); 170 $this->assertEquals( 200, $response->get_status() ); 171 $this->assertNull( get_post( $this->revision_id1 ) ); 172 } 173 174 public function test_delete_item_no_permission() { 175 wp_set_current_user( $this->contributor_id ); 176 $request = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 ); 177 $response = $this->server->dispatch( $request ); 178 $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); 179 } 180 181 public function test_prepare_item() { 182 wp_set_current_user( $this->editor_id ); 183 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 ); 184 $response = $this->server->dispatch( $request ); 185 $this->assertEquals( 200, $response->get_status() ); 186 $this->check_get_revision_response( $response, $this->revision_1 ); 187 } 188 189 public function test_get_item_schema() { 190 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id . '/revisions' ); 191 $response = $this->server->dispatch( $request ); 192 $data = $response->get_data(); 193 $properties = $data['schema']['properties']; 194 $this->assertEquals( 12, count( $properties ) ); 195 $this->assertArrayHasKey( 'author', $properties ); 196 $this->assertArrayHasKey( 'content', $properties ); 197 $this->assertArrayHasKey( 'date', $properties ); 198 $this->assertArrayHasKey( 'date_gmt', $properties ); 199 $this->assertArrayHasKey( 'excerpt', $properties ); 200 $this->assertArrayHasKey( 'guid', $properties ); 201 $this->assertArrayHasKey( 'id', $properties ); 202 $this->assertArrayHasKey( 'modified', $properties ); 203 $this->assertArrayHasKey( 'modified_gmt', $properties ); 204 $this->assertArrayHasKey( 'parent', $properties ); 205 $this->assertArrayHasKey( 'slug', $properties ); 206 $this->assertArrayHasKey( 'title', $properties ); 207 } 208 209 public function test_create_item() { 210 $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . $this->post_id . '/revisions' ); 211 $response = $this->server->dispatch( $request ); 212 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 213 } 214 215 public function test_update_item() { 216 $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 ); 217 $response = $this->server->dispatch( $request ); 218 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 219 } 220 221 public function test_get_additional_field_registration() { 222 223 $schema = array( 224 'type' => 'integer', 225 'description' => 'Some integer of mine', 226 'enum' => array( 1, 2, 3, 4 ), 227 'context' => array( 'view', 'edit' ), 228 ); 229 230 register_rest_field( 'post-revision', 'my_custom_int', array( 231 'schema' => $schema, 232 'get_callback' => array( $this, 'additional_field_get_callback' ), 233 'update_callback' => array( $this, 'additional_field_update_callback' ), 234 ) ); 235 236 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id . '/revisions' ); 237 238 $response = $this->server->dispatch( $request ); 239 $data = $response->get_data(); 240 241 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 242 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 243 244 wp_set_current_user( 1 ); 245 246 $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 ); 247 248 $response = $this->server->dispatch( $request ); 249 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 250 251 global $wp_rest_additional_fields; 252 $wp_rest_additional_fields = array(); 253 } 254 255 public function additional_field_get_callback( $object ) { 256 return get_post_meta( $object['id'], 'my_custom_int', true ); 257 } 258 259 public function additional_field_update_callback( $value, $post ) { 260 update_post_meta( $post->ID, 'my_custom_int', $value ); 261 } 262 263 protected function check_get_revision_response( $response, $revision ) { 264 if ( $response instanceof WP_REST_Response ) { 265 $links = $response->get_links(); 266 $response = $response->get_data(); 267 } else { 268 $this->assertArrayHasKey( '_links', $response ); 269 $links = $response['_links']; 270 } 271 272 $this->assertEquals( $revision->post_author, $response['author'] ); 273 274 $rendered_content = apply_filters( 'the_content', $revision->post_content ); 275 $this->assertEquals( $rendered_content, $response['content']['rendered'] ); 276 277 $this->assertEquals( mysql_to_rfc3339( $revision->post_date ), $response['date'] ); 278 $this->assertEquals( mysql_to_rfc3339( $revision->post_date_gmt ), $response['date_gmt'] ); 279 280 $rendered_excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $revision->post_excerpt, $revision ) ); 281 $this->assertEquals( $rendered_excerpt, $response['excerpt']['rendered'] ); 282 283 $rendered_guid = apply_filters( 'get_the_guid', $revision->guid ); 284 $this->assertEquals( $rendered_guid, $response['guid']['rendered'] ); 285 286 $this->assertEquals( $revision->ID, $response['id'] ); 287 $this->assertEquals( mysql_to_rfc3339( $revision->post_modified ), $response['modified'] ); 288 $this->assertEquals( mysql_to_rfc3339( $revision->post_modified_gmt ), $response['modified_gmt'] ); 289 $this->assertEquals( $revision->post_name, $response['slug'] ); 290 291 $rendered_title = get_the_title( $revision->ID ); 292 $this->assertEquals( $rendered_title, $response['title']['rendered'] ); 293 294 $parent = get_post( $revision->post_parent ); 295 $parent_controller = new WP_REST_Posts_Controller( $parent->post_type ); 296 $parent_object = get_post_type_object( $parent->post_type ); 297 $parent_base = ! empty( $parent_object->rest_base ) ? $parent_object->rest_base : $parent_object->name; 298 $this->assertEquals( rest_url( '/wp/v2/' . $parent_base . '/' . $revision->post_parent ), $links['parent'][0]['href'] ); 299 } 300 301 } -
tests/phpunit/tests/rest-api/rest-settings-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_Test_REST_Settings_Controller functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase { 13 14 public function setUp() { 15 global $wp_version; 16 if ( version_compare( $wp_version, '4.7-alpha', '<' ) ) { 17 return $this->markTestSkipped( 'WordPress version not supported.' ); 18 } 19 parent::setUp(); 20 $this->administrator = $this->factory->user->create( array( 21 'role' => 'administrator', 22 ) ); 23 $this->endpoint = new WP_REST_Settings_Controller(); 24 } 25 26 public function test_register_routes() { 27 $routes = $this->server->get_routes(); 28 $this->assertArrayHasKey( '/wp/v2/settings', $routes ); 29 } 30 31 public function test_get_items() { 32 } 33 34 public function test_context_param() { 35 } 36 37 public function test_get_item_is_not_public() { 38 $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 39 $response = $this->server->dispatch( $request ); 40 $this->assertEquals( 403, $response->get_status() ); 41 } 42 43 public function test_get_item() { 44 wp_set_current_user( $this->administrator ); 45 $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 46 $response = $this->server->dispatch( $request ); 47 $data = $response->get_data(); 48 49 $this->assertEquals( 200, $response->get_status() ); 50 $this->assertEquals( array( 51 'title', 52 'description', 53 'url', 54 'email', 55 'timezone', 56 'date_format', 57 'time_format', 58 'start_of_week', 59 'language', 60 'use_smilies', 61 'default_category', 62 'default_post_format', 63 'posts_per_page', 64 ), array_keys( $data ) ); 65 } 66 67 public function test_get_item_value_is_cast_to_type() { 68 wp_set_current_user( $this->administrator ); 69 update_option( 'posts_per_page', 'invalid_number' ); // this is cast to (int) 1 70 $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 71 $response = $this->server->dispatch( $request ); 72 $data = $response->get_data(); 73 74 $this->assertEquals( 200, $response->get_status() ); 75 $this->assertEquals( 1, $data['posts_per_page'] ); 76 } 77 78 public function test_get_item_with_custom_setting() { 79 wp_set_current_user( $this->administrator ); 80 81 register_setting( 'somegroup', 'mycustomsetting', array( 82 'show_in_rest' => array( 83 'name' => 'mycustomsettinginrest', 84 'schema' => array( 85 'enum' => array( 'validvalue1', 'validvalue2' ), 86 'default' => 'validvalue1', 87 ), 88 ), 89 'type' => 'string', 90 ) ); 91 92 $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 93 $response = $this->server->dispatch( $request ); 94 $data = $response->get_data(); 95 96 $this->assertEquals( 200, $response->get_status() ); 97 $this->assertArrayHasKey( 'mycustomsettinginrest', $data ); 98 $this->assertEquals( 'validvalue1', $data['mycustomsettinginrest'] ); 99 100 update_option( 'mycustomsetting', 'validvalue2' ); 101 102 $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 103 $response = $this->server->dispatch( $request ); 104 $data = $response->get_data(); 105 $this->assertEquals( 'validvalue2', $data['mycustomsettinginrest'] ); 106 107 unregister_setting( 'somegroup', 'mycustomsetting' ); 108 } 109 110 public function get_setting_custom_callback( $result, $name, $args ) { 111 switch ( $name ) { 112 case 'mycustomsetting1': 113 return 'filtered1'; 114 } 115 return $result; 116 } 117 118 public function test_get_item_with_filter() { 119 wp_set_current_user( $this->administrator ); 120 121 add_filter( 'rest_pre_get_setting', array( $this, 'get_setting_custom_callback' ), 10, 3 ); 122 123 register_setting( 'somegroup', 'mycustomsetting1', array( 124 'show_in_rest' => array( 125 'name' => 'mycustomsettinginrest1', 126 ), 127 'type' => 'string', 128 ) ); 129 130 register_setting( 'somegroup', 'mycustomsetting2', array( 131 'show_in_rest' => array( 132 'name' => 'mycustomsettinginrest2', 133 ), 134 'type' => 'string', 135 ) ); 136 137 update_option( 'mycustomsetting1', 'unfiltered1' ); 138 update_option( 'mycustomsetting2', 'unfiltered2' ); 139 140 $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 141 $response = $this->server->dispatch( $request ); 142 $data = $response->get_data(); 143 144 $this->assertEquals( 200, $response->get_status() ); 145 146 $this->assertArrayHasKey( 'mycustomsettinginrest1', $data ); 147 $this->assertEquals( 'unfiltered1', $data['mycustomsettinginrest1'] ); 148 149 $this->assertArrayHasKey( 'mycustomsettinginrest2', $data ); 150 $this->assertEquals( 'unfiltered2', $data['mycustomsettinginrest2'] ); 151 152 unregister_setting( 'somegroup', 'mycustomsetting' ); 153 remove_all_filters( 'rest_pre_get_setting' ); 154 } 155 156 public function test_create_item() { 157 } 158 159 public function test_update_item() { 160 wp_set_current_user( $this->administrator ); 161 $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 162 $request->set_param( 'title', 'The new title!' ); 163 $response = $this->server->dispatch( $request ); 164 $data = $response->get_data(); 165 166 $this->assertEquals( 200, $response->get_status() ); 167 $this->assertEquals( 'The new title!', $data['title'] ); 168 $this->assertEquals( get_option( 'blogname' ), $data['title'] ); 169 } 170 171 public function update_setting_custom_callback( $result, $name, $value, $args ) { 172 if ( 'title' === $name && 'The new title!' === $value ) { 173 // Do not allow changing the title in this case 174 return true; 175 } 176 177 return false; 178 } 179 180 public function test_update_item_with_filter() { 181 wp_set_current_user( $this->administrator ); 182 183 $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 184 $request->set_param( 'title', 'The old title!' ); 185 $request->set_param( 'description', 'The old description!' ); 186 $response = $this->server->dispatch( $request ); 187 $data = $response->get_data(); 188 $this->assertEquals( 200, $response->get_status() ); 189 $this->assertEquals( 'The old title!', $data['title'] ); 190 $this->assertEquals( 'The old description!', $data['description'] ); 191 $this->assertEquals( get_option( 'blogname' ), $data['title'] ); 192 $this->assertEquals( get_option( 'blogdescription' ), $data['description'] ); 193 194 add_filter( 'rest_pre_update_setting', array( $this, 'update_setting_custom_callback' ), 10, 4 ); 195 196 $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 197 $request->set_param( 'title', 'The new title!' ); 198 $request->set_param( 'description', 'The new description!' ); 199 $response = $this->server->dispatch( $request ); 200 $data = $response->get_data(); 201 202 $this->assertEquals( 200, $response->get_status() ); 203 $this->assertEquals( 'The old title!', $data['title'] ); 204 $this->assertEquals( 'The new description!', $data['description'] ); 205 $this->assertEquals( get_option( 'blogname' ), $data['title'] ); 206 $this->assertEquals( get_option( 'blogdescription' ), $data['description'] ); 207 208 remove_all_filters( 'rest_pre_update_setting' ); 209 } 210 211 public function test_update_item_with_invalid_type() { 212 wp_set_current_user( $this->administrator ); 213 $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 214 $request->set_param( 'title', array( 'rendered' => 'This should fail.' ) ); 215 $response = $this->server->dispatch( $request ); 216 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 217 } 218 219 /** 220 * Setting an item to "null" will essentially restore it to it's default value. 221 */ 222 public function test_update_item_with_null() { 223 update_option( 'posts_per_page', 9 ); 224 225 wp_set_current_user( $this->administrator ); 226 $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 227 $request->set_param( 'posts_per_page', null ); 228 $response = $this->server->dispatch( $request ); 229 $data = $response->get_data(); 230 231 $this->assertEquals( 200, $response->get_status() ); 232 $this->assertEquals( 10, $data['posts_per_page'] ); 233 } 234 235 public function test_delete_item() { 236 } 237 238 public function test_prepare_item() { 239 } 240 241 public function test_get_item_schema() { 242 } 243 } -
tests/phpunit/tests/rest-api/rest-tags-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Terms_Controller functionality, used for Tags. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Tags_Controller extends WP_Test_REST_Controller_Testcase { 13 14 public function setUp() { 15 parent::setUp(); 16 $this->administrator = $this->factory->user->create( array( 17 'role' => 'administrator', 18 ) ); 19 $this->subscriber = $this->factory->user->create( array( 20 'role' => 'subscriber', 21 ) ); 22 } 23 24 public function test_register_routes() { 25 $routes = $this->server->get_routes(); 26 $this->assertArrayHasKey( '/wp/v2/tags', $routes ); 27 $this->assertArrayHasKey( '/wp/v2/tags/(?P<id>[\d]+)', $routes ); 28 } 29 30 public function test_context_param() { 31 // Collection 32 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' ); 33 $response = $this->server->dispatch( $request ); 34 $data = $response->get_data(); 35 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 36 $this->assertEqualSets( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 37 // Single 38 $tag1 = $this->factory->tag->create( array( 'name' => 'Season 5' ) ); 39 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags/' . $tag1 ); 40 $response = $this->server->dispatch( $request ); 41 $data = $response->get_data(); 42 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 43 $this->assertEqualSets( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 44 } 45 46 public function test_registered_query_params() { 47 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' ); 48 $response = $this->server->dispatch( $request ); 49 $data = $response->get_data(); 50 $keys = array_keys( $data['endpoints'][0]['args'] ); 51 sort( $keys ); 52 $this->assertEquals( array( 53 'context', 54 'exclude', 55 'hide_empty', 56 'include', 57 'offset', 58 'order', 59 'orderby', 60 'page', 61 'per_page', 62 'post', 63 'search', 64 'slug', 65 ), $keys ); 66 } 67 68 public function test_get_items() { 69 $this->factory->tag->create(); 70 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 71 $response = $this->server->dispatch( $request ); 72 $this->check_get_taxonomy_terms_response( $response ); 73 } 74 75 public function test_get_items_invalid_permission_for_context() { 76 wp_set_current_user( 0 ); 77 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 78 $request->set_param( 'context', 'edit' ); 79 $response = $this->server->dispatch( $request ); 80 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 81 } 82 83 public function test_get_items_hide_empty_arg() { 84 $post_id = $this->factory->post->create(); 85 $tag1 = $this->factory->tag->create( array( 'name' => 'Season 5' ) ); 86 $tag2 = $this->factory->tag->create( array( 'name' => 'The Be Sharps' ) ); 87 wp_set_object_terms( $post_id, array( $tag1, $tag2 ), 'post_tag' ); 88 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 89 $request->set_param( 'hide_empty', true ); 90 $response = $this->server->dispatch( $request ); 91 $data = $response->get_data(); 92 $this->assertEquals( 2, count( $data ) ); 93 $this->assertEquals( 'Season 5', $data[0]['name'] ); 94 $this->assertEquals( 'The Be Sharps', $data[1]['name'] ); 95 } 96 97 public function test_get_items_include_query() { 98 $id1 = $this->factory->tag->create(); 99 $id2 = $this->factory->tag->create(); 100 $id3 = $this->factory->tag->create(); 101 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 102 // Orderby=>asc 103 $request->set_param( 'include', array( $id3, $id1 ) ); 104 $response = $this->server->dispatch( $request ); 105 $data = $response->get_data(); 106 $this->assertEquals( 2, count( $data ) ); 107 $this->assertEquals( $id1, $data[0]['id'] ); 108 // Orderby=>include 109 $request->set_param( 'orderby', 'include' ); 110 $response = $this->server->dispatch( $request ); 111 $data = $response->get_data(); 112 $this->assertEquals( 2, count( $data ) ); 113 $this->assertEquals( $id3, $data[0]['id'] ); 114 } 115 116 public function test_get_items_exclude_query() { 117 $id1 = $this->factory->tag->create(); 118 $id2 = $this->factory->tag->create(); 119 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 120 $response = $this->server->dispatch( $request ); 121 $data = $response->get_data(); 122 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 123 $this->assertTrue( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 124 $request->set_param( 'exclude', array( $id2 ) ); 125 $response = $this->server->dispatch( $request ); 126 $data = $response->get_data(); 127 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 128 $this->assertFalse( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 129 } 130 131 public function test_get_items_offset_query() { 132 $id1 = $this->factory->tag->create(); 133 $id2 = $this->factory->tag->create(); 134 $id3 = $this->factory->tag->create(); 135 $id4 = $this->factory->tag->create(); 136 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 137 $request->set_param( 'offset', 1 ); 138 $response = $this->server->dispatch( $request ); 139 $this->assertCount( 3, $response->get_data() ); 140 // 'offset' works with 'per_page' 141 $request->set_param( 'per_page', 2 ); 142 $response = $this->server->dispatch( $request ); 143 $this->assertCount( 2, $response->get_data() ); 144 // 'offset' takes priority over 'page' 145 $request->set_param( 'page', 3 ); 146 $response = $this->server->dispatch( $request ); 147 $this->assertCount( 2, $response->get_data() ); 148 } 149 150 151 public function test_get_items_orderby_args() { 152 $tag1 = $this->factory->tag->create( array( 'name' => 'Apple' ) ); 153 $tag2 = $this->factory->tag->create( array( 'name' => 'Banana' ) ); 154 /* 155 * Tests: 156 * - orderby 157 * - order 158 * - per_page 159 */ 160 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 161 $request->set_param( 'orderby', 'name' ); 162 $request->set_param( 'order', 'desc' ); 163 $request->set_param( 'per_page', 1 ); 164 $response = $this->server->dispatch( $request ); 165 $this->assertEquals( 200, $response->get_status() ); 166 $data = $response->get_data(); 167 $this->assertEquals( 1, count( $data ) ); 168 $this->assertEquals( 'Banana', $data[0]['name'] ); 169 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 170 $request->set_param( 'orderby', 'name' ); 171 $request->set_param( 'order', 'asc' ); 172 $request->set_param( 'per_page', 2 ); 173 $response = $this->server->dispatch( $request ); 174 $this->assertEquals( 200, $response->get_status() ); 175 $data = $response->get_data(); 176 $this->assertEquals( 2, count( $data ) ); 177 $this->assertEquals( 'Apple', $data[0]['name'] ); 178 } 179 180 public function test_get_items_orderby_id() { 181 $tag0 = $this->factory->tag->create( array( 'name' => 'Cantaloupe' ) ); 182 $tag1 = $this->factory->tag->create( array( 'name' => 'Apple' ) ); 183 $tag2 = $this->factory->tag->create( array( 'name' => 'Banana' ) ); 184 // defaults to orderby=name, order=asc 185 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 186 $response = $this->server->dispatch( $request ); 187 $this->assertEquals( 200, $response->get_status() ); 188 $data = $response->get_data(); 189 $this->assertEquals( 'Apple', $data[0]['name'] ); 190 $this->assertEquals( 'Banana', $data[1]['name'] ); 191 $this->assertEquals( 'Cantaloupe', $data[2]['name'] ); 192 // orderby=id, with default order=asc 193 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 194 $request->set_param( 'orderby', 'id' ); 195 $response = $this->server->dispatch( $request ); 196 $this->assertEquals( 200, $response->get_status() ); 197 $data = $response->get_data(); 198 $this->assertEquals( 'Cantaloupe', $data[0]['name'] ); 199 $this->assertEquals( 'Apple', $data[1]['name'] ); 200 $this->assertEquals( 'Banana', $data[2]['name'] ); 201 // orderby=id, order=desc 202 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 203 $request->set_param( 'orderby', 'id' ); 204 $request->set_param( 'order', 'desc' ); 205 $response = $this->server->dispatch( $request ); 206 $data = $response->get_data(); 207 $this->assertEquals( 200, $response->get_status() ); 208 $this->assertEquals( 'Banana', $data[0]['name'] ); 209 $this->assertEquals( 'Apple', $data[1]['name'] ); 210 $this->assertEquals( 'Cantaloupe', $data[2]['name'] ); 211 } 212 213 public function test_get_items_post_args() { 214 $post_id = $this->factory->post->create(); 215 $tag1 = $this->factory->tag->create( array( 'name' => 'DC' ) ); 216 $tag2 = $this->factory->tag->create( array( 'name' => 'Marvel' ) ); 217 $this->factory->tag->create( array( 'name' => 'Dark Horse' ) ); 218 wp_set_object_terms( $post_id, array( $tag1, $tag2 ), 'post_tag' ); 219 220 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 221 $request->set_param( 'post', $post_id ); 222 $response = $this->server->dispatch( $request ); 223 $this->assertEquals( 200, $response->get_status() ); 224 225 $data = $response->get_data(); 226 $this->assertEquals( 2, count( $data ) ); 227 $this->assertEquals( 'DC', $data[0]['name'] ); 228 } 229 230 public function test_get_terms_post_args_paging() { 231 $post_id = $this->factory->post->create(); 232 $tag_ids = array(); 233 234 for ( $i = 0; $i < 30; $i++ ) { 235 $tag_ids[] = $this->factory->tag->create( array( 236 'name' => "Tag {$i}", 237 ) ); 238 } 239 wp_set_object_terms( $post_id, $tag_ids, 'post_tag' ); 240 241 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 242 $request->set_param( 'post', $post_id ); 243 $request->set_param( 'page', 1 ); 244 $request->set_param( 'per_page', 15 ); 245 $request->set_param( 'orderby', 'id' ); 246 $response = $this->server->dispatch( $request ); 247 $tags = $response->get_data(); 248 249 $i = 0; 250 foreach ( $tags as $tag ) { 251 $this->assertEquals( $tag['name'], "Tag {$i}" ); 252 $i++; 253 } 254 255 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 256 $request->set_param( 'post', $post_id ); 257 $request->set_param( 'page', 2 ); 258 $request->set_param( 'per_page', 15 ); 259 $request->set_param( 'orderby', 'id' ); 260 $response = $this->server->dispatch( $request ); 261 $tags = $response->get_data(); 262 263 foreach ( $tags as $tag ) { 264 $this->assertEquals( $tag['name'], "Tag {$i}" ); 265 $i++; 266 } 267 } 268 269 public function test_get_items_post_empty() { 270 $post_id = $this->factory->post->create(); 271 272 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 273 $request->set_param( 'post', $post_id ); 274 $response = $this->server->dispatch( $request ); 275 $this->assertEquals( 200, $response->get_status() ); 276 277 $data = $response->get_data(); 278 $this->assertCount( 0, $data ); 279 } 280 281 public function test_get_items_custom_tax_post_args() { 282 register_taxonomy( 'batman', 'post', array( 'show_in_rest' => true ) ); 283 $controller = new WP_REST_Terms_Controller( 'batman' ); 284 $controller->register_routes(); 285 $term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'batman' ) ); 286 $term2 = $this->factory->term->create( array( 'name' => 'Mask', 'taxonomy' => 'batman' ) ); 287 $this->factory->term->create( array( 'name' => 'Car', 'taxonomy' => 'batman' ) ); 288 $post_id = $this->factory->post->create(); 289 wp_set_object_terms( $post_id, array( $term1, $term2 ), 'batman' ); 290 291 $request = new WP_REST_Request( 'GET', '/wp/v2/batman' ); 292 $request->set_param( 'post', $post_id ); 293 $response = $this->server->dispatch( $request ); 294 $this->assertEquals( 200, $response->get_status() ); 295 296 $data = $response->get_data(); 297 $this->assertEquals( 2, count( $data ) ); 298 $this->assertEquals( 'Cape', $data[0]['name'] ); 299 } 300 301 public function test_get_items_search_args() { 302 $tag1 = $this->factory->tag->create( array( 'name' => 'Apple' ) ); 303 $tag2 = $this->factory->tag->create( array( 'name' => 'Banana' ) ); 304 /* 305 * Tests: 306 * - search 307 */ 308 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 309 $request->set_param( 'search', 'App' ); 310 $response = $this->server->dispatch( $request ); 311 $this->assertEquals( 200, $response->get_status() ); 312 $data = $response->get_data(); 313 $this->assertEquals( 1, count( $data ) ); 314 $this->assertEquals( 'Apple', $data[0]['name'] ); 315 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 316 $request->set_param( 'search', 'Garbage' ); 317 $response = $this->server->dispatch( $request ); 318 $this->assertEquals( 200, $response->get_status() ); 319 $data = $response->get_data(); 320 $this->assertEquals( 0, count( $data ) ); 321 } 322 323 public function test_get_items_slug_arg() { 324 $tag1 = $this->factory->tag->create( array( 'name' => 'Apple' ) ); 325 $tag2 = $this->factory->tag->create( array( 'name' => 'Banana' ) ); 326 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 327 $request->set_param( 'slug', 'apple' ); 328 $response = $this->server->dispatch( $request ); 329 $this->assertEquals( 200, $response->get_status() ); 330 $data = $response->get_data(); 331 $this->assertEquals( 1, count( $data ) ); 332 $this->assertEquals( 'Apple', $data[0]['name'] ); 333 } 334 335 public function test_get_terms_private_taxonomy() { 336 register_taxonomy( 'robin', 'post', array( 'public' => false ) ); 337 $term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) ); 338 $term2 = $this->factory->term->create( array( 'name' => 'Mask', 'taxonomy' => 'robin' ) ); 339 340 $request = new WP_REST_Request( 'GET', '/wp/v2/terms/robin' ); 341 $response = $this->server->dispatch( $request ); 342 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 343 } 344 345 public function test_get_terms_pagination_headers() { 346 // Start of the index 347 for ( $i = 0; $i < 50; $i++ ) { 348 $this->factory->tag->create( array( 349 'name' => "Tag {$i}", 350 ) ); 351 } 352 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 353 $response = $this->server->dispatch( $request ); 354 $headers = $response->get_headers(); 355 $this->assertEquals( 50, $headers['X-WP-Total'] ); 356 $this->assertEquals( 5, $headers['X-WP-TotalPages'] ); 357 $next_link = add_query_arg( array( 358 'page' => 2, 359 ), rest_url( 'wp/v2/tags' ) ); 360 $this->assertFalse( stripos( $headers['Link'], 'rel="prev"' ) ); 361 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 362 // 3rd page 363 $this->factory->tag->create( array( 364 'name' => 'Tag 51', 365 ) ); 366 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 367 $request->set_param( 'page', 3 ); 368 $response = $this->server->dispatch( $request ); 369 $headers = $response->get_headers(); 370 $this->assertEquals( 51, $headers['X-WP-Total'] ); 371 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 372 $prev_link = add_query_arg( array( 373 'page' => 2, 374 ), rest_url( 'wp/v2/tags' ) ); 375 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 376 $next_link = add_query_arg( array( 377 'page' => 4, 378 ), rest_url( 'wp/v2/tags' ) ); 379 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 380 // Last page 381 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 382 $request->set_param( 'page', 6 ); 383 $response = $this->server->dispatch( $request ); 384 $headers = $response->get_headers(); 385 $this->assertEquals( 51, $headers['X-WP-Total'] ); 386 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 387 $prev_link = add_query_arg( array( 388 'page' => 5, 389 ), rest_url( 'wp/v2/tags' ) ); 390 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 391 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 392 // Out of bounds 393 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 394 $request->set_param( 'page', 8 ); 395 $response = $this->server->dispatch( $request ); 396 $headers = $response->get_headers(); 397 $this->assertEquals( 51, $headers['X-WP-Total'] ); 398 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 399 $prev_link = add_query_arg( array( 400 'page' => 6, 401 ), rest_url( 'wp/v2/tags' ) ); 402 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 403 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 404 } 405 406 public function test_get_items_invalid_context() { 407 $request = new WP_REST_Request( 'GET', '/wp/v2/tags' ); 408 $request->set_param( 'context', 'banana' ); 409 $response = $this->server->dispatch( $request ); 410 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 411 } 412 413 public function test_get_item() { 414 $id = $this->factory->tag->create(); 415 $request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $id ); 416 $response = $this->server->dispatch( $request ); 417 $this->check_get_taxonomy_term_response( $response, $id ); 418 } 419 420 public function test_get_term_invalid_term() { 421 $request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 422 $response = $this->server->dispatch( $request ); 423 $this->assertErrorResponse( 'rest_term_invalid', $response, 404 ); 424 } 425 426 public function test_get_item_invalid_permission_for_context() { 427 $id = $this->factory->tag->create(); 428 wp_set_current_user( 0 ); 429 $request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $id ); 430 $request->set_param( 'context', 'edit' ); 431 $response = $this->server->dispatch( $request ); 432 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 433 } 434 435 public function test_get_term_private_taxonomy() { 436 register_taxonomy( 'robin', 'post', array( 'public' => false ) ); 437 $term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) ); 438 439 $request = new WP_REST_Request( 'GET', '/wp/v2/terms/robin/' . $term1 ); 440 $response = $this->server->dispatch( $request ); 441 $this->assertErrorResponse( 'rest_no_route', $response, 404 ); 442 } 443 444 public function test_get_item_incorrect_taxonomy() { 445 register_taxonomy( 'robin', 'post' ); 446 $term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) ); 447 $request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $term1 ); 448 $response = $this->server->dispatch( $request ); 449 $this->assertErrorResponse( 'rest_term_invalid', $response, 404 ); 450 } 451 452 public function test_create_item() { 453 wp_set_current_user( $this->administrator ); 454 $request = new WP_REST_Request( 'POST', '/wp/v2/tags' ); 455 $request->set_param( 'name', 'My Awesome Term' ); 456 $request->set_param( 'description', 'This term is so awesome.' ); 457 $request->set_param( 'slug', 'so-awesome' ); 458 $response = $this->server->dispatch( $request ); 459 $this->assertEquals( 201, $response->get_status() ); 460 $headers = $response->get_headers(); 461 $data = $response->get_data(); 462 $this->assertContains( '/wp/v2/tags/' . $data['id'], $headers['Location'] ); 463 $this->assertEquals( 'My Awesome Term', $data['name'] ); 464 $this->assertEquals( 'This term is so awesome.', $data['description'] ); 465 $this->assertEquals( 'so-awesome', $data['slug'] ); 466 } 467 468 public function test_create_item_incorrect_permissions() { 469 wp_set_current_user( $this->subscriber ); 470 $request = new WP_REST_Request( 'POST', '/wp/v2/tags' ); 471 $request->set_param( 'name', 'Incorrect permissions' ); 472 $response = $this->server->dispatch( $request ); 473 $this->assertErrorResponse( 'rest_cannot_create', $response, 403 ); 474 } 475 476 public function test_create_item_missing_arguments() { 477 wp_set_current_user( $this->administrator ); 478 $request = new WP_REST_Request( 'POST', '/wp/v2/tags' ); 479 $response = $this->server->dispatch( $request ); 480 $this->assertErrorResponse( 'rest_missing_callback_param', $response, 400 ); 481 } 482 483 public function test_create_item_parent_non_hierarchical_taxonomy() { 484 wp_set_current_user( $this->administrator ); 485 486 $request = new WP_REST_Request( 'POST', '/wp/v2/tags' ); 487 $request->set_param( 'name', 'My Awesome Term' ); 488 $request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 489 $response = $this->server->dispatch( $request ); 490 $this->assertErrorResponse( 'rest_taxonomy_not_hierarchical', $response, 400 ); 491 } 492 493 public function test_update_item() { 494 wp_set_current_user( $this->administrator ); 495 $orig_args = array( 496 'name' => 'Original Name', 497 'description' => 'Original Description', 498 'slug' => 'original-slug', 499 ); 500 $term = get_term_by( 'id', $this->factory->tag->create( $orig_args ), 'post_tag' ); 501 $request = new WP_REST_Request( 'POST', '/wp/v2/tags/' . $term->term_id ); 502 $request->set_param( 'name', 'New Name' ); 503 $request->set_param( 'description', 'New Description' ); 504 $request->set_param( 'slug', 'new-slug' ); 505 $response = $this->server->dispatch( $request ); 506 $this->assertEquals( 200, $response->get_status() ); 507 $data = $response->get_data(); 508 $this->assertEquals( 'New Name', $data['name'] ); 509 $this->assertEquals( 'New Description', $data['description'] ); 510 $this->assertEquals( 'new-slug', $data['slug'] ); 511 } 512 513 public function test_update_item_invalid_term() { 514 wp_set_current_user( $this->administrator ); 515 $request = new WP_REST_Request( 'POST', '/wp/v2/tags/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 516 $request->set_param( 'name', 'Invalid Term' ); 517 $response = $this->server->dispatch( $request ); 518 $this->assertErrorResponse( 'rest_term_invalid', $response, 404 ); 519 } 520 521 public function test_update_item_incorrect_permissions() { 522 wp_set_current_user( $this->subscriber ); 523 $term = get_term_by( 'id', $this->factory->tag->create(), 'post_tag' ); 524 $request = new WP_REST_Request( 'POST', '/wp/v2/tags/' . $term->term_id ); 525 $request->set_param( 'name', 'Incorrect permissions' ); 526 $response = $this->server->dispatch( $request ); 527 $this->assertErrorResponse( 'rest_cannot_update', $response, 403 ); 528 } 529 530 public function test_update_item_parent_non_hierarchical_taxonomy() { 531 wp_set_current_user( $this->administrator ); 532 $term = get_term_by( 'id', $this->factory->tag->create(), 'post_tag' ); 533 534 $request = new WP_REST_Request( 'POST', '/wp/v2/tags/' . $term->term_taxonomy_id ); 535 $request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 536 $response = $this->server->dispatch( $request ); 537 $this->assertErrorResponse( 'rest_taxonomy_not_hierarchical', $response, 400 ); 538 } 539 540 public function test_delete_item() { 541 wp_set_current_user( $this->administrator ); 542 $term = get_term_by( 'id', $this->factory->tag->create( array( 'name' => 'Deleted Tag' ) ), 'post_tag' ); 543 $request = new WP_REST_Request( 'DELETE', '/wp/v2/tags/' . $term->term_id ); 544 $request->set_param( 'force', true ); 545 $response = $this->server->dispatch( $request ); 546 $this->assertEquals( 200, $response->get_status() ); 547 $data = $response->get_data(); 548 $this->assertEquals( 'Deleted Tag', $data['name'] ); 549 } 550 551 public function test_delete_item_force_false() { 552 wp_set_current_user( $this->administrator ); 553 $term = get_term_by( 'id', $this->factory->tag->create( array( 'name' => 'Deleted Tag' ) ), 'post_tag' ); 554 $request = new WP_REST_Request( 'DELETE', '/wp/v2/tags/' . $term->term_id ); 555 // force defaults to false 556 $response = $this->server->dispatch( $request ); 557 $this->assertEquals( 501, $response->get_status() ); 558 } 559 560 public function test_delete_item_invalid_term() { 561 wp_set_current_user( $this->administrator ); 562 $request = new WP_REST_Request( 'DELETE', '/wp/v2/tags/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); 563 $response = $this->server->dispatch( $request ); 564 $this->assertErrorResponse( 'rest_term_invalid', $response, 404 ); 565 } 566 567 public function test_delete_item_incorrect_permissions() { 568 wp_set_current_user( $this->subscriber ); 569 $term = get_term_by( 'id', $this->factory->tag->create(), 'post_tag' ); 570 $request = new WP_REST_Request( 'DELETE', '/wp/v2/tags/' . $term->term_id ); 571 $response = $this->server->dispatch( $request ); 572 $this->assertErrorResponse( 'rest_cannot_delete', $response, 403 ); 573 } 574 575 public function test_prepare_item() { 576 $term = get_term_by( 'id', $this->factory->tag->create(), 'post_tag' ); 577 $request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $term->term_id ); 578 $response = $this->server->dispatch( $request ); 579 $data = $response->get_data(); 580 581 $this->check_taxonomy_term( $term, $data, $response->get_links() ); 582 } 583 584 public function test_get_item_schema() { 585 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' ); 586 $response = $this->server->dispatch( $request ); 587 $data = $response->get_data(); 588 $properties = $data['schema']['properties']; 589 $this->assertEquals( 8, count( $properties ) ); 590 $this->assertArrayHasKey( 'id', $properties ); 591 $this->assertArrayHasKey( 'count', $properties ); 592 $this->assertArrayHasKey( 'description', $properties ); 593 $this->assertArrayHasKey( 'link', $properties ); 594 $this->assertArrayHasKey( 'meta', $properties ); 595 $this->assertArrayHasKey( 'name', $properties ); 596 $this->assertArrayHasKey( 'slug', $properties ); 597 $this->assertArrayHasKey( 'taxonomy', $properties ); 598 $this->assertEquals( array_keys( get_taxonomies() ), $properties['taxonomy']['enum'] ); 599 } 600 601 public function test_get_item_schema_non_hierarchical() { 602 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' ); 603 $response = $this->server->dispatch( $request ); 604 $data = $response->get_data(); 605 $properties = $data['schema']['properties']; 606 $this->assertArrayHasKey( 'id', $properties ); 607 $this->assertFalse( isset( $properties['parent'] ) ); 608 } 609 610 public function test_get_additional_field_registration() { 611 612 $schema = array( 613 'type' => 'integer', 614 'description' => 'Some integer of mine', 615 'enum' => array( 1, 2, 3, 4 ), 616 'context' => array( 'view', 'edit' ), 617 ); 618 619 register_rest_field( 'tag', 'my_custom_int', array( 620 'schema' => $schema, 621 'get_callback' => array( $this, 'additional_field_get_callback' ), 622 ) ); 623 624 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' ); 625 626 $response = $this->server->dispatch( $request ); 627 $data = $response->get_data(); 628 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 629 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 630 631 $tag_id = $this->factory->tag->create(); 632 $request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $tag_id ); 633 634 $response = $this->server->dispatch( $request ); 635 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 636 637 global $wp_rest_additional_fields; 638 $wp_rest_additional_fields = array(); 639 } 640 641 public function test_additional_field_update_errors() { 642 $schema = array( 643 'type' => 'integer', 644 'description' => 'Some integer of mine', 645 'enum' => array( 1, 2, 3, 4 ), 646 'context' => array( 'view', 'edit' ), 647 ); 648 649 register_rest_field( 'tag', 'my_custom_int', array( 650 'schema' => $schema, 651 'get_callback' => array( $this, 'additional_field_get_callback' ), 652 'update_callback' => array( $this, 'additional_field_update_callback' ), 653 ) ); 654 655 wp_set_current_user( $this->administrator ); 656 $tag_id = $this->factory->tag->create(); 657 // Check for error on update. 658 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/tags/%d', $tag_id ) ); 659 $request->set_body_params( array( 660 'my_custom_int' => 'returnError', 661 ) ); 662 663 $response = $this->server->dispatch( $request ); 664 665 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 666 667 global $wp_rest_additional_fields; 668 $wp_rest_additional_fields = array(); 669 } 670 671 public function additional_field_get_callback( $object, $request ) { 672 return 123; 673 } 674 675 public function additional_field_update_callback( $value, $tag ) { 676 if ( 'returnError' === $value ) { 677 return new WP_Error( 'rest_invalid_param', 'Testing an error.', array( 'status' => 400 ) ); 678 } 679 } 680 681 public function tearDown() { 682 _unregister_taxonomy( 'batman' ); 683 _unregister_taxonomy( 'robin' ); 684 parent::tearDown(); 685 } 686 687 protected function check_get_taxonomy_terms_response( $response ) { 688 $this->assertEquals( 200, $response->get_status() ); 689 $data = $response->get_data(); 690 $args = array( 691 'hide_empty' => false, 692 ); 693 $tags = get_terms( 'post_tag', $args ); 694 $this->assertEquals( count( $tags ), count( $data ) ); 695 $this->assertEquals( $tags[0]->term_id, $data[0]['id'] ); 696 $this->assertEquals( $tags[0]->name, $data[0]['name'] ); 697 $this->assertEquals( $tags[0]->slug, $data[0]['slug'] ); 698 $this->assertEquals( $tags[0]->taxonomy, $data[0]['taxonomy'] ); 699 $this->assertEquals( $tags[0]->description, $data[0]['description'] ); 700 $this->assertEquals( $tags[0]->count, $data[0]['count'] ); 701 } 702 703 protected function check_taxonomy_term( $term, $data, $links ) { 704 $this->assertEquals( $term->term_id, $data['id'] ); 705 $this->assertEquals( $term->name, $data['name'] ); 706 $this->assertEquals( $term->slug, $data['slug'] ); 707 $this->assertEquals( $term->description, $data['description'] ); 708 $this->assertEquals( get_term_link( $term ), $data['link'] ); 709 $this->assertEquals( $term->count, $data['count'] ); 710 $taxonomy = get_taxonomy( $term->taxonomy ); 711 if ( $taxonomy->hierarchical ) { 712 $this->assertEquals( $term->parent, $data['parent'] ); 713 } else { 714 $this->assertFalse( isset( $data['parent'] ) ); 715 } 716 $expected_links = array( 717 'self', 718 'collection', 719 'about', 720 'https://api.w.org/post_type', 721 ); 722 if ( $taxonomy->hierarchical && $term->parent ) { 723 $expected_links[] = 'up'; 724 } 725 $this->assertEqualSets( $expected_links, array_keys( $links ) ); 726 $this->assertContains( 'wp/v2/taxonomies/' . $term->taxonomy, $links['about'][0]['href'] ); 727 $this->assertEquals( add_query_arg( 'tags', $term->term_id, rest_url( 'wp/v2/posts' ) ), $links['https://api.w.org/post_type'][0]['href'] ); 728 } 729 730 protected function check_get_taxonomy_term_response( $response, $id ) { 731 732 $this->assertEquals( 200, $response->get_status() ); 733 734 $data = $response->get_data(); 735 $tag = get_term( $id, 'post_tag' ); 736 $this->check_taxonomy_term( $tag, $data, $response->get_links() ); 737 } 738 } -
tests/phpunit/tests/rest-api/rest-taxonomies-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Taxonomies_Controller functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Taxonomies_Controller extends WP_Test_REST_Controller_Testcase { 13 14 public function test_register_routes() { 15 $routes = $this->server->get_routes(); 16 17 $this->assertArrayHasKey( '/wp/v2/taxonomies', $routes ); 18 $this->assertArrayHasKey( '/wp/v2/taxonomies/(?P<taxonomy>[\w-]+)', $routes ); 19 } 20 21 public function test_context_param() { 22 // Collection 23 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/taxonomies' ); 24 $response = $this->server->dispatch( $request ); 25 $data = $response->get_data(); 26 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 27 $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); 28 // Single 29 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/taxonomies/post_tag' ); 30 $response = $this->server->dispatch( $request ); 31 $data = $response->get_data(); 32 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 33 $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); 34 } 35 36 public function test_get_items() { 37 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); 38 $response = $this->server->dispatch( $request ); 39 $data = $response->get_data(); 40 $taxonomies = $this->get_public_taxonomies( get_taxonomies( '', 'objects' ) ); 41 $this->assertEquals( count( $taxonomies ), count( $data ) ); 42 $this->assertEquals( 'Categories', $data['category']['name'] ); 43 $this->assertEquals( 'category', $data['category']['slug'] ); 44 $this->assertEquals( true, $data['category']['hierarchical'] ); 45 $this->assertEquals( 'Tags', $data['post_tag']['name'] ); 46 $this->assertEquals( 'post_tag', $data['post_tag']['slug'] ); 47 $this->assertEquals( false, $data['post_tag']['hierarchical'] ); 48 } 49 50 public function test_get_items_invalid_permission_for_context() { 51 wp_set_current_user( 0 ); 52 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); 53 $request->set_param( 'context', 'edit' ); 54 $response = $this->server->dispatch( $request ); 55 $this->assertErrorResponse( 'rest_cannot_view', $response, 401 ); 56 } 57 58 public function test_get_taxonomies_for_type() { 59 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); 60 $request->set_param( 'type', 'post' ); 61 $response = $this->server->dispatch( $request ); 62 $this->check_taxonomies_for_type_response( 'post', $response ); 63 } 64 65 public function test_get_taxonomies_for_invalid_type() { 66 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); 67 $request->set_param( 'type', 'wingding' ); 68 $response = $this->server->dispatch( $request ); 69 $this->assertEquals( 200, $response->get_status() ); 70 $data = $response->get_data(); 71 $this->assertEquals( '{}', json_encode( $data ) ); 72 } 73 74 public function test_get_item() { 75 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' ); 76 $response = $this->server->dispatch( $request ); 77 $this->check_taxonomy_object_response( 'view', $response ); 78 } 79 80 public function test_get_item_edit_context() { 81 $editor_id = $this->factory->user->create( array( 'role' => 'editor' ) ); 82 wp_set_current_user( $editor_id ); 83 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' ); 84 $request->set_param( 'context', 'edit' ); 85 $response = $this->server->dispatch( $request ); 86 $this->check_taxonomy_object_response( 'edit', $response ); 87 } 88 89 public function test_get_item_invalid_permission_for_context() { 90 wp_set_current_user( 0 ); 91 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' ); 92 $request->set_param( 'context', 'edit' ); 93 $response = $this->server->dispatch( $request ); 94 $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); 95 } 96 97 public function test_get_invalid_taxonomy() { 98 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/invalid' ); 99 $response = $this->server->dispatch( $request ); 100 $this->assertErrorResponse( 'rest_taxonomy_invalid', $response, 404 ); 101 } 102 103 public function test_get_non_public_taxonomy() { 104 register_taxonomy( 'api-private', 'post', array( 'public' => false ) ); 105 106 $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/api-private' ); 107 $response = $this->server->dispatch( $request ); 108 $this->assertErrorResponse( 'rest_forbidden', $response, 403 ); 109 } 110 111 public function test_create_item() { 112 /** Taxonomies can't be created **/ 113 } 114 115 public function test_update_item() { 116 /** Taxonomies can't be updated **/ 117 } 118 119 public function test_delete_item() { 120 /** Taxonomies can't be deleted **/ 121 } 122 123 public function test_prepare_item() { 124 $tax = get_taxonomy( 'category' ); 125 $endpoint = new WP_REST_Taxonomies_Controller; 126 $request = new WP_REST_Request; 127 $request->set_param( 'context', 'edit' ); 128 $response = $endpoint->prepare_item_for_response( $tax, $request ); 129 $this->check_taxonomy_object( 'edit', $tax, $response->get_data(), $response->get_links() ); 130 } 131 132 public function test_get_item_schema() { 133 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/taxonomies' ); 134 $response = $this->server->dispatch( $request ); 135 $data = $response->get_data(); 136 $properties = $data['schema']['properties']; 137 $this->assertEquals( 8, count( $properties ) ); 138 $this->assertArrayHasKey( 'capabilities', $properties ); 139 $this->assertArrayHasKey( 'description', $properties ); 140 $this->assertArrayHasKey( 'hierarchical', $properties ); 141 $this->assertArrayHasKey( 'labels', $properties ); 142 $this->assertArrayHasKey( 'name', $properties ); 143 $this->assertArrayHasKey( 'slug', $properties ); 144 $this->assertArrayHasKey( 'show_cloud', $properties ); 145 $this->assertArrayHasKey( 'types', $properties ); 146 } 147 148 public function tearDown() { 149 parent::tearDown(); 150 } 151 152 /** 153 * Utility function for use in get_public_taxonomies 154 */ 155 private function is_public( $taxonomy ) { 156 return ! empty( $taxonomy->show_in_rest ); 157 } 158 /** 159 * Utility function to filter down to only public taxonomies 160 */ 161 private function get_public_taxonomies( $taxonomies ) { 162 // Pass through array_values to re-index after filtering 163 return array_values( array_filter( $taxonomies, array( $this, 'is_public' ) ) ); 164 } 165 166 protected function check_taxonomy_object( $context, $tax_obj, $data, $links ) { 167 $this->assertEquals( $tax_obj->label, $data['name'] ); 168 $this->assertEquals( $tax_obj->name, $data['slug'] ); 169 $this->assertEquals( $tax_obj->description, $data['description'] ); 170 $this->assertEquals( $tax_obj->hierarchical, $data['hierarchical'] ); 171 $this->assertEquals( rest_url( 'wp/v2/taxonomies' ), $links['collection'][0]['href'] ); 172 $this->assertArrayHasKey( 'https://api.w.org/items', $links ); 173 if ( 'edit' === $context ) { 174 $this->assertEquals( $tax_obj->cap, $data['capabilities'] ); 175 $this->assertEquals( $tax_obj->labels, $data['labels'] ); 176 $this->assertEquals( $tax_obj->show_tagcloud, $data['show_cloud'] ); 177 } else { 178 $this->assertFalse( isset( $data['capabilities'] ) ); 179 $this->assertFalse( isset( $data['labels'] ) ); 180 $this->assertFalse( isset( $data['show_cloud'] ) ); 181 } 182 } 183 184 protected function check_taxonomy_object_response( $context, $response ) { 185 $this->assertEquals( 200, $response->get_status() ); 186 $data = $response->get_data(); 187 $category = get_taxonomy( 'category' ); 188 $this->check_taxonomy_object( $context, $category, $data, $response->get_links() ); 189 } 190 191 protected function check_taxonomies_for_type_response( $type, $response ) { 192 $this->assertEquals( 200, $response->get_status() ); 193 $data = $response->get_data(); 194 $taxonomies = $this->get_public_taxonomies( get_object_taxonomies( $type, 'objects' ) ); 195 $this->assertEquals( count( $taxonomies ), count( $data ) ); 196 } 197 198 } -
tests/phpunit/tests/rest-api/rest-test-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Controller functionality 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_REST_Test_Controller extends WP_REST_Controller { 13 14 /** 15 * Get the Post type's schema, conforming to JSON Schema 16 * 17 * @return array 18 */ 19 public function get_item_schema() { 20 $schema = array( 21 '$schema' => 'http://json-schema.org/draft-04/schema#', 22 'title' => 'type', 23 'type' => 'object', 24 'properties' => array( 25 'somestring' => array( 26 'type' => 'string', 27 'description' => 'A pretty string.', 28 'context' => array( 'view' ), 29 ), 30 'someinteger' => array( 31 'type' => 'integer', 32 'context' => array( 'view' ), 33 ), 34 'someboolean' => array( 35 'type' => 'boolean', 36 'context' => array( 'view' ), 37 ), 38 'someurl' => array( 39 'type' => 'string', 40 'format' => 'uri', 41 'context' => array( 'view' ), 42 ), 43 'somedate' => array( 44 'type' => 'string', 45 'format' => 'date-time', 46 'context' => array( 'view' ), 47 ), 48 'someemail' => array( 49 'type' => 'string', 50 'format' => 'email', 51 'context' => array( 'view' ), 52 ), 53 'someenum' => array( 54 'type' => 'string', 55 'enum' => array( 'a', 'b', 'c' ), 56 'context' => array( 'view' ), 57 ), 58 'someargoptions' => array( 59 'type' => 'integer', 60 'required' => true, 61 'arg_options' => array( 62 'required' => false, 63 'sanitize_callback' => '__return_true', 64 ), 65 ), 66 'somedefault' => array( 67 'type' => 'string', 68 'enum' => array( 'a', 'b', 'c' ), 69 'context' => array( 'view' ), 70 'default' => 'a', 71 ), 72 ), 73 ); 74 75 return $schema; 76 } 77 78 } -
tests/phpunit/tests/rest-api/rest-users-controller.php
1 <?php 2 /** 3 * Unit tests covering WP_REST_Users_Controller functionality. 4 * 5 * @package WordPress 6 * @subpackage REST API 7 */ 8 9 /** 10 * @group restapi 11 */ 12 class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { 13 /** 14 * This function is run before each method 15 */ 16 public function setUp() { 17 parent::setUp(); 18 19 $this->user = $this->factory->user->create( array( 20 'role' => 'administrator', 21 ) ); 22 23 $this->editor = $this->factory->user->create( array( 24 'role' => 'editor', 25 'user_email' => 'editor@example.com', 26 ) ); 27 28 $this->endpoint = new WP_REST_Users_Controller(); 29 } 30 31 public function test_register_routes() { 32 $routes = $this->server->get_routes(); 33 34 $this->assertArrayHasKey( '/wp/v2/users', $routes ); 35 $this->assertCount( 2, $routes['/wp/v2/users'] ); 36 $this->assertArrayHasKey( '/wp/v2/users/(?P<id>[\d]+)', $routes ); 37 $this->assertCount( 3, $routes['/wp/v2/users/(?P<id>[\d]+)'] ); 38 $this->assertArrayHasKey( '/wp/v2/users/me', $routes ); 39 } 40 41 public function test_context_param() { 42 // Collection 43 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/users' ); 44 $response = $this->server->dispatch( $request ); 45 $data = $response->get_data(); 46 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 47 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 48 // Single 49 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/users/' . $this->user ); 50 $response = $this->server->dispatch( $request ); 51 $data = $response->get_data(); 52 $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); 53 $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); 54 } 55 56 public function test_registered_query_params() { 57 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/users' ); 58 $response = $this->server->dispatch( $request ); 59 $data = $response->get_data(); 60 $keys = array_keys( $data['endpoints'][0]['args'] ); 61 sort( $keys ); 62 $this->assertEquals( array( 63 'context', 64 'exclude', 65 'include', 66 'offset', 67 'order', 68 'orderby', 69 'page', 70 'per_page', 71 'roles', 72 'search', 73 'slug', 74 ), $keys ); 75 } 76 77 public function test_get_items() { 78 wp_set_current_user( $this->user ); 79 80 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 81 $request->set_param( 'context', 'view' ); 82 $response = $this->server->dispatch( $request ); 83 84 $this->assertEquals( 200, $response->get_status() ); 85 86 $all_data = $response->get_data(); 87 $data = $all_data[0]; 88 $userdata = get_userdata( $data['id'] ); 89 $this->check_user_data( $userdata, $data, 'view', $data['_links'] ); 90 } 91 92 public function test_get_items_with_edit_context() { 93 wp_set_current_user( $this->user ); 94 95 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 96 $request->set_param( 'context', 'edit' ); 97 $response = $this->server->dispatch( $request ); 98 99 $this->assertEquals( 200, $response->get_status() ); 100 101 $all_data = $response->get_data(); 102 $data = $all_data[0]; 103 $userdata = get_userdata( $data['id'] ); 104 $this->check_user_data( $userdata, $data, 'edit', $data['_links'] ); 105 } 106 107 public function test_get_items_with_edit_context_without_permission() { 108 //test with a user not logged in 109 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 110 $request->set_param( 'context', 'edit' ); 111 $response = $this->server->dispatch( $request ); 112 113 $this->assertEquals( 401, $response->get_status() ); 114 115 //test with a user logged in but without sufficient capabilities; capability in question: 'list_users' 116 wp_set_current_user( $this->editor ); 117 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 118 $request->set_param( 'context', 'edit' ); 119 $response = $this->server->dispatch( $request ); 120 121 $this->assertEquals( 403, $response->get_status() ); 122 } 123 124 public function test_get_items_unauthenticated_only_shows_public_users() { 125 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 126 $response = $this->server->dispatch( $request ); 127 128 $this->assertEquals( array(), $response->get_data() ); 129 130 $this->factory->post->create( array( 'post_author' => $this->editor ) ); 131 $this->factory->post->create( array( 'post_author' => $this->user, 'post_status' => 'draft' ) ); 132 133 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 134 $response = $this->server->dispatch( $request ); 135 $users = $response->get_data(); 136 137 foreach ( $users as $user ) { 138 $this->assertTrue( count_user_posts( $user['id'] ) > 0 ); 139 140 // Ensure we don't expose non-public data 141 $this->assertArrayNotHasKey( 'capabilities', $user ); 142 $this->assertArrayNotHasKey( 'email', $user ); 143 $this->assertArrayNotHasKey( 'roles', $user ); 144 } 145 } 146 147 /** 148 * @group test 149 */ 150 public function test_get_items_pagination_headers() { 151 wp_set_current_user( $this->user ); 152 // Start of the index, including the three existing users 153 for ( $i = 0; $i < 47; $i++ ) { 154 $this->factory->user->create( array( 155 'name' => "User {$i}", 156 ) ); 157 } 158 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 159 $response = $this->server->dispatch( $request ); 160 $headers = $response->get_headers(); 161 $this->assertEquals( 50, $headers['X-WP-Total'] ); 162 $this->assertEquals( 5, $headers['X-WP-TotalPages'] ); 163 $next_link = add_query_arg( array( 164 'page' => 2, 165 ), rest_url( 'wp/v2/users' ) ); 166 $this->assertFalse( stripos( $headers['Link'], 'rel="prev"' ) ); 167 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 168 // 3rd page 169 $this->factory->user->create( array( 170 'name' => 'User 51', 171 ) ); 172 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 173 $request->set_param( 'page', 3 ); 174 $response = $this->server->dispatch( $request ); 175 $headers = $response->get_headers(); 176 $this->assertEquals( 51, $headers['X-WP-Total'] ); 177 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 178 $prev_link = add_query_arg( array( 179 'page' => 2, 180 ), rest_url( 'wp/v2/users' ) ); 181 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 182 $next_link = add_query_arg( array( 183 'page' => 4, 184 ), rest_url( 'wp/v2/users' ) ); 185 $this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] ); 186 // Last page 187 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 188 $request->set_param( 'page', 6 ); 189 $response = $this->server->dispatch( $request ); 190 $headers = $response->get_headers(); 191 $this->assertEquals( 51, $headers['X-WP-Total'] ); 192 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 193 $prev_link = add_query_arg( array( 194 'page' => 5, 195 ), rest_url( 'wp/v2/users' ) ); 196 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 197 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 198 // Out of bounds 199 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 200 $request->set_param( 'page', 8 ); 201 $response = $this->server->dispatch( $request ); 202 $headers = $response->get_headers(); 203 $this->assertEquals( 51, $headers['X-WP-Total'] ); 204 $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); 205 $prev_link = add_query_arg( array( 206 'page' => 6, 207 ), rest_url( 'wp/v2/users' ) ); 208 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 209 $this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) ); 210 } 211 212 public function test_get_items_per_page() { 213 wp_set_current_user( $this->user ); 214 for ( $i = 0; $i < 20; $i++ ) { 215 $this->factory->user->create( array( 'display_name' => "User {$i}" ) ); 216 } 217 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 218 $response = $this->server->dispatch( $request ); 219 $this->assertEquals( 10, count( $response->get_data() ) ); 220 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 221 $request->set_param( 'per_page', 5 ); 222 $response = $this->server->dispatch( $request ); 223 $this->assertEquals( 5, count( $response->get_data() ) ); 224 } 225 226 public function test_get_items_page() { 227 wp_set_current_user( $this->user ); 228 for ( $i = 0; $i < 20; $i++ ) { 229 $this->factory->user->create( array( 'display_name' => "User {$i}" ) ); 230 } 231 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 232 $request->set_param( 'per_page', 5 ); 233 $request->set_param( 'page', 2 ); 234 $response = $this->server->dispatch( $request ); 235 $this->assertEquals( 5, count( $response->get_data() ) ); 236 $prev_link = add_query_arg( array( 237 'per_page' => 5, 238 'page' => 1, 239 ), rest_url( 'wp/v2/users' ) ); 240 $headers = $response->get_headers(); 241 $this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] ); 242 } 243 244 public function test_get_items_orderby_name() { 245 wp_set_current_user( $this->user ); 246 $low_id = $this->factory->user->create( array( 'display_name' => 'AAAAA' ) ); 247 $mid_id = $this->factory->user->create( array( 'display_name' => 'NNNNN' ) ); 248 $high_id = $this->factory->user->create( array( 'display_name' => 'ZZZZ' ) ); 249 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 250 $request->set_param( 'orderby', 'name' ); 251 $request->set_param( 'order', 'desc' ); 252 $request->set_param( 'per_page', 1 ); 253 $response = $this->server->dispatch( $request ); 254 $data = $response->get_data(); 255 $this->assertEquals( $high_id, $data[0]['id'] ); 256 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 257 $request->set_param( 'orderby', 'name' ); 258 $request->set_param( 'order', 'asc' ); 259 $request->set_param( 'per_page', 1 ); 260 $response = $this->server->dispatch( $request ); 261 $data = $response->get_data(); 262 $this->assertEquals( $low_id, $data[0]['id'] ); 263 } 264 265 public function test_get_items_orderby_url() { 266 wp_set_current_user( $this->user ); 267 268 $low_id = $this->factory->user->create( array( 'user_url' => 'http://a.com' ) ); 269 $high_id = $this->factory->user->create( array( 'user_url' => 'http://b.com' ) ); 270 271 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 272 $request->set_param( 'orderby', 'url' ); 273 $request->set_param( 'order', 'desc' ); 274 $request->set_param( 'per_page', 1 ); 275 $request->set_param( 'include', array( $low_id, $high_id ) ); 276 $response = $this->server->dispatch( $request ); 277 $data = $response->get_data(); 278 279 $this->assertEquals( $high_id, $data[0]['id'] ); 280 281 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 282 $request->set_param( 'orderby', 'url' ); 283 $request->set_param( 'order', 'asc' ); 284 $request->set_param( 'per_page', 1 ); 285 $request->set_param( 'include', array( $low_id, $high_id ) ); 286 $response = $this->server->dispatch( $request ); 287 $data = $response->get_data(); 288 $this->assertEquals( $low_id, $data[0]['id'] ); 289 } 290 291 public function test_get_items_orderby_slug() { 292 wp_set_current_user( $this->user ); 293 294 $high_id = $this->factory->user->create( array( 'user_nicename' => 'blogin' ) ); 295 $low_id = $this->factory->user->create( array( 'user_nicename' => 'alogin' ) ); 296 297 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 298 $request->set_param( 'orderby', 'slug' ); 299 $request->set_param( 'order', 'desc' ); 300 $request->set_param( 'per_page', 1 ); 301 $request->set_param( 'include', array( $low_id, $high_id ) ); 302 $response = $this->server->dispatch( $request ); 303 $data = $response->get_data(); 304 305 $this->assertEquals( $high_id, $data[0]['id'] ); 306 307 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 308 $request->set_param( 'orderby', 'slug' ); 309 $request->set_param( 'order', 'asc' ); 310 $request->set_param( 'per_page', 1 ); 311 $request->set_param( 'include', array( $low_id, $high_id ) ); 312 $response = $this->server->dispatch( $request ); 313 $data = $response->get_data(); 314 $this->assertEquals( $low_id, $data[0]['id'] ); 315 } 316 317 public function test_get_items_orderby_email() { 318 wp_set_current_user( $this->user ); 319 320 $high_id = $this->factory->user->create( array( 'user_email' => 'bemail@gmail.com' ) ); 321 $low_id = $this->factory->user->create( array( 'user_email' => 'aemail@gmail.com' ) ); 322 323 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 324 $request->set_param( 'orderby', 'email' ); 325 $request->set_param( 'order', 'desc' ); 326 $request->set_param( 'per_page', 1 ); 327 $request->set_param( 'include', array( $low_id, $high_id ) ); 328 $response = $this->server->dispatch( $request ); 329 $data = $response->get_data(); 330 $this->assertEquals( $high_id, $data[0]['id'] ); 331 332 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 333 $request->set_param( 'orderby', 'email' ); 334 $request->set_param( 'order', 'asc' ); 335 $request->set_param( 'per_page', 1 ); 336 $request->set_param( 'include', array( $low_id, $high_id ) ); 337 $response = $this->server->dispatch( $request ); 338 $data = $response->get_data(); 339 $this->assertEquals( $low_id, $data[0]['id'] ); 340 } 341 342 public function test_get_items_orderby_email_unauthenticated() { 343 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 344 $request->set_param( 'orderby', 'email' ); 345 $request->set_param( 'order', 'desc' ); 346 $response = $this->server->dispatch( $request ); 347 $this->assertErrorResponse( 'rest_forbidden_orderby', $response, 401 ); 348 } 349 350 public function test_get_items_orderby_registered_date_unauthenticated() { 351 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 352 $request->set_param( 'orderby', 'registered_date' ); 353 $request->set_param( 'order', 'desc' ); 354 $response = $this->server->dispatch( $request ); 355 $this->assertErrorResponse( 'rest_forbidden_orderby', $response, 401 ); 356 } 357 358 public function test_get_items_offset() { 359 wp_set_current_user( $this->user ); 360 // 2 users created in __construct(), plus default user 361 $this->factory->user->create(); 362 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 363 $request->set_param( 'offset', 1 ); 364 $response = $this->server->dispatch( $request ); 365 $this->assertCount( 3, $response->get_data() ); 366 // 'offset' works with 'per_page' 367 $request->set_param( 'per_page', 2 ); 368 $response = $this->server->dispatch( $request ); 369 $this->assertCount( 2, $response->get_data() ); 370 // 'offset' takes priority over 'page' 371 $request->set_param( 'page', 3 ); 372 $response = $this->server->dispatch( $request ); 373 $this->assertCount( 2, $response->get_data() ); 374 } 375 376 public function test_get_items_include_query() { 377 wp_set_current_user( $this->user ); 378 $id1 = $this->factory->user->create(); 379 $id2 = $this->factory->user->create(); 380 $id3 = $this->factory->user->create(); 381 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 382 // Orderby=>asc 383 $request->set_param( 'include', array( $id3, $id1 ) ); 384 $response = $this->server->dispatch( $request ); 385 $data = $response->get_data(); 386 $this->assertEquals( 2, count( $data ) ); 387 $this->assertEquals( $id1, $data[0]['id'] ); 388 // Orderby=>include 389 $request->set_param( 'orderby', 'include' ); 390 $response = $this->server->dispatch( $request ); 391 $data = $response->get_data(); 392 $this->assertEquals( 2, count( $data ) ); 393 $this->assertEquals( $id3, $data[0]['id'] ); 394 // No privileges 395 wp_set_current_user( 0 ); 396 $response = $this->server->dispatch( $request ); 397 $data = $response->get_data(); 398 $this->assertEquals( 0, count( $data ) ); 399 400 } 401 402 public function test_get_items_exclude_query() { 403 wp_set_current_user( $this->user ); 404 $id1 = $this->factory->user->create(); 405 $id2 = $this->factory->user->create(); 406 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 407 $response = $this->server->dispatch( $request ); 408 $data = $response->get_data(); 409 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 410 $this->assertTrue( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 411 $request->set_param( 'exclude', array( $id2 ) ); 412 $response = $this->server->dispatch( $request ); 413 $data = $response->get_data(); 414 $this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) ); 415 $this->assertFalse( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) ); 416 } 417 418 public function test_get_items_search() { 419 wp_set_current_user( $this->user ); 420 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 421 $request->set_param( 'search', 'yololololo' ); 422 $response = $this->server->dispatch( $request ); 423 $this->assertEquals( 0, count( $response->get_data() ) ); 424 $yolo_id = $this->factory->user->create( array( 'display_name' => 'yololololo' ) ); 425 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 426 $request->set_param( 'search', (string) $yolo_id ); 427 $response = $this->server->dispatch( $request ); 428 $this->assertEquals( 1, count( $response->get_data() ) ); 429 // default to wildcard search 430 $adam_id = $this->factory->user->create( array( 431 'role' => 'author', 432 'user_nicename' => 'adam', 433 ) ); 434 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 435 $request->set_param( 'search', 'ada' ); 436 $response = $this->server->dispatch( $request ); 437 $data = $response->get_data(); 438 $this->assertEquals( 1, count( $data ) ); 439 $this->assertEquals( $adam_id, $data[0]['id'] ); 440 } 441 442 public function test_get_items_slug_query() { 443 wp_set_current_user( $this->user ); 444 $this->factory->user->create( array( 'display_name' => 'foo', 'user_login' => 'bar' ) ); 445 $id2 = $this->factory->user->create( array( 'display_name' => 'Moo', 'user_login' => 'foo' ) ); 446 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 447 $request->set_param( 'slug', 'foo' ); 448 $response = $this->server->dispatch( $request ); 449 $data = $response->get_data(); 450 $this->assertEquals( 1, count( $data ) ); 451 $this->assertEquals( $id2, $data[0]['id'] ); 452 } 453 454 // Note: Do not test using editor role as there is an editor role created in testing and it makes it hard to test this functionality. 455 public function test_get_items_roles() { 456 wp_set_current_user( $this->user ); 457 $tango = $this->factory->user->create( array( 'display_name' => 'tango', 'role' => 'subscriber' ) ); 458 $yolo = $this->factory->user->create( array( 'display_name' => 'yolo', 'role' => 'author' ) ); 459 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 460 $request->set_param( 'roles', 'author,subscriber' ); 461 $response = $this->server->dispatch( $request ); 462 $data = $response->get_data(); 463 $this->assertEquals( 2, count( $data ) ); 464 $this->assertEquals( $tango, $data[0]['id'] ); 465 $this->assertEquals( $yolo, $data[1]['id'] ); 466 $request->set_param( 'roles', 'author' ); 467 $response = $this->server->dispatch( $request ); 468 $data = $response->get_data(); 469 $this->assertEquals( 1, count( $data ) ); 470 $this->assertEquals( $yolo, $data[0]['id'] ); 471 wp_set_current_user( 0 ); 472 $request->set_param( 'roles', 'author' ); 473 $response = $this->server->dispatch( $request ); 474 $this->assertErrorResponse( 'rest_user_cannot_view', $response, 401 ); 475 wp_set_current_user( $this->editor ); 476 $request->set_param( 'roles', 'author' ); 477 $response = $this->server->dispatch( $request ); 478 $this->assertErrorResponse( 'rest_user_cannot_view', $response, 403 ); 479 } 480 481 public function test_get_items_invalid_roles() { 482 wp_set_current_user( $this->user ); 483 $lolz = $this->factory->user->create( array( 'display_name' => 'lolz', 'role' => 'author' ) ); 484 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 485 $request->set_param( 'roles', 'ilovesteak,author' ); 486 $response = $this->server->dispatch( $request ); 487 $data = $response->get_data(); 488 $this->assertEquals( 1, count( $data ) ); 489 $this->assertEquals( $lolz, $data[0]['id'] ); 490 $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); 491 $request->set_param( 'roles', 'steakisgood' ); 492 $response = $this->server->dispatch( $request ); 493 $data = $response->get_data(); 494 $this->assertEquals( 0, count( $data ) ); 495 $this->assertEquals( array(), $data ); 496 } 497 498 public function test_get_item() { 499 $user_id = $this->factory->user->create(); 500 wp_set_current_user( $this->user ); 501 502 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/users/%d', $user_id ) ); 503 504 $response = $this->server->dispatch( $request ); 505 $this->check_get_user_response( $response, 'embed' ); 506 } 507 508 public function test_prepare_item() { 509 wp_set_current_user( $this->user ); 510 $request = new WP_REST_Request; 511 $request->set_param( 'context', 'edit' ); 512 $user = get_user_by( 'id', get_current_user_id() ); 513 $data = $this->endpoint->prepare_item_for_response( $user, $request ); 514 $this->check_get_user_response( $data, 'edit' ); 515 } 516 517 public function test_get_user_avatar_urls() { 518 wp_set_current_user( $this->user ); 519 520 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/users/%d', $this->editor ) ); 521 522 $response = $this->server->dispatch( $request ); 523 524 $data = $response->get_data(); 525 $this->assertArrayHasKey( 24, $data['avatar_urls'] ); 526 $this->assertArrayHasKey( 48, $data['avatar_urls'] ); 527 $this->assertArrayHasKey( 96, $data['avatar_urls'] ); 528 529 $user = get_user_by( 'id', $this->editor ); 530 /** 531 * Ignore the subdomain, since 'get_avatar_url randomly sets the Gravatar 532 * server when building the url string. 533 */ 534 $this->assertEquals( substr( get_avatar_url( $user->user_email ), 9 ), substr( $data['avatar_urls'][96], 9 ) ); 535 } 536 537 public function test_get_user_invalid_id() { 538 wp_set_current_user( $this->user ); 539 $request = new WP_REST_Request( 'GET', '/wp/v2/users/100' ); 540 $response = $this->server->dispatch( $request ); 541 542 $this->assertErrorResponse( 'rest_user_invalid_id', $response, 404 ); 543 } 544 545 public function test_get_user_empty_capabilities() { 546 wp_set_current_user( $this->user ); 547 $this->allow_user_to_manage_multisite(); 548 549 $lolz = $this->factory->user->create( array( 'display_name' => 'lolz', 'roles' => '' ) ); 550 delete_user_option( $lolz, 'capabilities' ); 551 delete_user_option( $lolz, 'user_level' ); 552 $request = new WP_REST_Request( 'GET', '/wp/v2/users/' . $lolz ); 553 $request->set_param( 'context', 'edit' ); 554 $response = $this->server->dispatch( $request ); 555 $data = $response->get_data(); 556 557 $this->assertEquals( $data['capabilities'], new stdClass() ); 558 $this->assertEquals( $data['extra_capabilities'], new stdClass() ); 559 } 560 561 public function test_get_item_without_permission() { 562 wp_set_current_user( $this->editor ); 563 564 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/users/%d', $this->user ) ); 565 $response = $this->server->dispatch( $request ); 566 567 $this->assertErrorResponse( 'rest_user_cannot_view', $response, 403 ); 568 } 569 570 public function test_get_item_published_author_post() { 571 $this->author_id = $this->factory->user->create( array( 572 'role' => 'author', 573 ) ); 574 $this->post_id = $this->factory->post->create( array( 575 'post_author' => $this->author_id, 576 )); 577 wp_set_current_user( 0 ); 578 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/users/%d', $this->author_id ) ); 579 $response = $this->server->dispatch( $request ); 580 $this->check_get_user_response( $response, 'embed' ); 581 } 582 583 public function test_get_item_published_author_pages() { 584 $this->author_id = $this->factory->user->create( array( 585 'role' => 'author', 586 ) ); 587 wp_set_current_user( 0 ); 588 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/users/%d', $this->author_id ) ); 589 $response = $this->server->dispatch( $request ); 590 $this->assertEquals( 401, $response->get_status() ); 591 $this->post_id = $this->factory->post->create( array( 592 'post_author' => $this->author_id, 593 'post_type' => 'page', 594 )); 595 $response = $this->server->dispatch( $request ); 596 $this->check_get_user_response( $response, 'embed' ); 597 } 598 599 public function test_get_user_with_edit_context() { 600 $user_id = $this->factory->user->create(); 601 $this->allow_user_to_manage_multisite(); 602 603 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/users/%d', $user_id ) ); 604 $request->set_param( 'context', 'edit' ); 605 606 $response = $this->server->dispatch( $request ); 607 $this->check_get_user_response( $response, 'edit' ); 608 } 609 610 public function test_get_item_published_author_wrong_context() { 611 $this->author_id = $this->factory->user->create( array( 612 'role' => 'author', 613 ) ); 614 $this->post_id = $this->factory->post->create( array( 615 'post_author' => $this->author_id, 616 )); 617 wp_set_current_user( 0 ); 618 $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/users/%d', $this->author_id ) ); 619 $request->set_param( 'context', 'edit' ); 620 $response = $this->server->dispatch( $request ); 621 $this->assertErrorResponse( 'rest_user_cannot_view', $response, 401 ); 622 } 623 624 public function test_get_current_user() { 625 wp_set_current_user( $this->user ); 626 627 $request = new WP_REST_Request( 'GET', '/wp/v2/users/me' ); 628 629 $response = $this->server->dispatch( $request ); 630 $this->assertEquals( 302, $response->get_status() ); 631 632 $headers = $response->get_headers(); 633 $this->assertArrayHasKey( 'Location', $headers ); 634 $this->assertEquals( rest_url( 'wp/v2/users/' . $this->user ), $headers['Location'] ); 635 } 636 637 public function test_get_current_user_without_permission() { 638 wp_set_current_user( 0 ); 639 $request = new WP_REST_Request( 'GET', '/wp/v2/users/me' ); 640 $response = $this->server->dispatch( $request ); 641 642 $this->assertErrorResponse( 'rest_not_logged_in', $response, 401 ); 643 } 644 645 public function test_create_item() { 646 $this->allow_user_to_manage_multisite(); 647 wp_set_current_user( $this->user ); 648 649 $params = array( 650 'username' => 'testuser', 651 'password' => 'testpassword', 652 'email' => 'test@example.com', 653 'name' => 'Test User', 654 'nickname' => 'testuser', 655 'slug' => 'test-user', 656 'role' => 'editor', 657 'description' => 'New API User', 658 'url' => 'http://example.com', 659 ); 660 661 $request = new WP_REST_Request( 'POST', '/wp/v2/users' ); 662 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 663 $request->set_body_params( $params ); 664 665 $response = $this->server->dispatch( $request ); 666 $data = $response->get_data(); 667 $this->assertEquals( 'http://example.com', $data['url'] ); 668 $this->check_add_edit_user_response( $response ); 669 } 670 671 public function test_json_create_user() { 672 $this->allow_user_to_manage_multisite(); 673 wp_set_current_user( $this->user ); 674 675 $params = array( 676 'username' => 'testjsonuser', 677 'password' => 'testjsonpassword', 678 'email' => 'testjson@example.com', 679 ); 680 681 $request = new WP_REST_Request( 'POST', '/wp/v2/users' ); 682 $request->add_header( 'content-type', 'application/json' ); 683 $request->set_body( wp_json_encode( $params ) ); 684 685 $response = $this->server->dispatch( $request ); 686 $this->check_add_edit_user_response( $response ); 687 } 688 689 public function test_create_user_without_permission() { 690 wp_set_current_user( $this->editor ); 691 692 $params = array( 693 'username' => 'homersimpson', 694 'password' => 'stupidsexyflanders', 695 'email' => 'chunkylover53@aol.com', 696 ); 697 698 $request = new WP_REST_Request( 'POST', '/wp/v2/users' ); 699 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 700 $request->set_body_params( $params ); 701 $response = $this->server->dispatch( $request ); 702 703 $this->assertErrorResponse( 'rest_cannot_create_user', $response, 403 ); 704 } 705 706 public function test_create_user_invalid_id() { 707 $this->allow_user_to_manage_multisite(); 708 wp_set_current_user( $this->user ); 709 710 $params = array( 711 'id' => '156', 712 'username' => 'lisasimpson', 713 'password' => 'DavidHasselhoff', 714 'email' => 'smartgirl63_@yahoo.com', 715 ); 716 717 $request = new WP_REST_Request( 'POST', '/wp/v2/users' ); 718 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 719 $request->set_body_params( $params ); 720 $response = $this->server->dispatch( $request ); 721 722 $this->assertErrorResponse( 'rest_user_exists', $response, 400 ); 723 } 724 725 public function test_create_user_invalid_email() { 726 $this->allow_user_to_manage_multisite(); 727 wp_set_current_user( $this->user ); 728 729 $params = array( 730 'username' => 'lisasimpson', 731 'password' => 'DavidHasselhoff', 732 'email' => 'something', 733 ); 734 735 $request = new WP_REST_Request( 'POST', '/wp/v2/users' ); 736 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 737 $request->set_body_params( $params ); 738 $response = $this->server->dispatch( $request ); 739 740 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 741 } 742 743 public function test_create_user_invalid_role() { 744 $this->allow_user_to_manage_multisite(); 745 wp_set_current_user( $this->user ); 746 747 $params = array( 748 'username' => 'maggiesimpson', 749 'password' => 'i_shot_mrburns', 750 'email' => 'packingheat@example.com', 751 'roles' => array( 'baby' ), 752 ); 753 754 $request = new WP_REST_Request( 'POST', '/wp/v2/users' ); 755 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 756 $request->set_body_params( $params ); 757 $response = $this->server->dispatch( $request ); 758 759 $this->assertErrorResponse( 'rest_user_invalid_role', $response, 400 ); 760 } 761 762 public function test_update_item() { 763 $user_id = $this->factory->user->create( array( 764 'user_email' => 'test@example.com', 765 'user_pass' => 'sjflsfls', 766 'user_login' => 'test_update', 767 'first_name' => 'Old Name', 768 'user_url' => 'http://apple.com', 769 )); 770 $this->allow_user_to_manage_multisite(); 771 wp_set_current_user( $this->user ); 772 773 $userdata = get_userdata( $user_id ); 774 $pw_before = $userdata->user_pass; 775 776 $_POST['email'] = $userdata->user_email; 777 $_POST['username'] = $userdata->user_login; 778 $_POST['first_name'] = 'New Name'; 779 $_POST['url'] = 'http://google.com'; 780 781 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $user_id ) ); 782 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 783 $request->set_body_params( $_POST ); 784 785 $response = $this->server->dispatch( $request ); 786 $this->check_add_edit_user_response( $response, true ); 787 788 // Check that the name has been updated correctly 789 $new_data = $response->get_data(); 790 $this->assertEquals( 'New Name', $new_data['first_name'] ); 791 $user = get_userdata( $user_id ); 792 $this->assertEquals( 'New Name', $user->first_name ); 793 794 $this->assertEquals( 'http://google.com', $new_data['url'] ); 795 $this->assertEquals( 'http://google.com', $user->user_url ); 796 797 // Check that we haven't inadvertently changed the user's password, 798 // as per https://core.trac.wordpress.org/ticket/21429 799 $this->assertEquals( $pw_before, $user->user_pass ); 800 } 801 802 public function test_update_item_existing_email() { 803 $user1 = $this->factory->user->create( array( 'user_login' => 'test_json_user', 'user_email' => 'testjson@example.com' ) ); 804 $user2 = $this->factory->user->create( array( 'user_login' => 'test_json_user2', 'user_email' => 'testjson2@example.com' ) ); 805 $this->allow_user_to_manage_multisite(); 806 wp_set_current_user( $this->user ); 807 808 $request = new WP_REST_Request( 'PUT', '/wp/v2/users/' . $user2 ); 809 $request->set_param( 'email', 'testjson@example.com' ); 810 $response = $this->server->dispatch( $request ); 811 $this->assertInstanceOf( 'WP_Error', $response->as_error() ); 812 $this->assertEquals( 'rest_user_invalid_email', $response->as_error()->get_error_code() ); 813 } 814 815 public function test_update_item_username_attempt() { 816 $user1 = $this->factory->user->create( array( 'user_login' => 'test_json_user', 'user_email' => 'testjson@example.com' ) ); 817 $user2 = $this->factory->user->create( array( 'user_login' => 'test_json_user2', 'user_email' => 'testjson2@example.com' ) ); 818 $this->allow_user_to_manage_multisite(); 819 wp_set_current_user( $this->user ); 820 821 $request = new WP_REST_Request( 'PUT', '/wp/v2/users/' . $user2 ); 822 $request->set_param( 'username', 'test_json_user' ); 823 $response = $this->server->dispatch( $request ); 824 $this->assertInstanceOf( 'WP_Error', $response->as_error() ); 825 $this->assertEquals( 'rest_user_invalid_argument', $response->as_error()->get_error_code() ); 826 } 827 828 public function test_update_item_existing_nicename() { 829 $user1 = $this->factory->user->create( array( 'user_login' => 'test_json_user', 'user_email' => 'testjson@example.com' ) ); 830 $user2 = $this->factory->user->create( array( 'user_login' => 'test_json_user2', 'user_email' => 'testjson2@example.com' ) ); 831 $this->allow_user_to_manage_multisite(); 832 wp_set_current_user( $this->user ); 833 834 $request = new WP_REST_Request( 'PUT', '/wp/v2/users/' . $user2 ); 835 $request->set_param( 'slug', 'test_json_user' ); 836 $response = $this->server->dispatch( $request ); 837 $this->assertInstanceOf( 'WP_Error', $response->as_error() ); 838 $this->assertEquals( 'rest_user_invalid_slug', $response->as_error()->get_error_code() ); 839 } 840 841 public function test_json_update_user() { 842 $user_id = $this->factory->user->create( array( 843 'user_email' => 'testjson2@example.com', 844 'user_pass' => 'sjflsfl3sdjls', 845 'user_login' => 'test_json_update', 846 'first_name' => 'Old Name', 847 'last_name' => 'Original Last', 848 )); 849 $this->allow_user_to_manage_multisite(); 850 wp_set_current_user( $this->user ); 851 852 $params = array( 853 'username' => 'test_json_update', 854 'email' => 'testjson2@example.com', 855 'first_name' => 'JSON Name', 856 'last_name' => 'New Last', 857 ); 858 859 $userdata = get_userdata( $user_id ); 860 $pw_before = $userdata->user_pass; 861 862 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $user_id ) ); 863 $request->add_header( 'content-type', 'application/json' ); 864 $request->set_body( wp_json_encode( $params ) ); 865 866 $response = $this->server->dispatch( $request ); 867 $this->check_add_edit_user_response( $response, true ); 868 869 // Check that the name has been updated correctly 870 $new_data = $response->get_data(); 871 $this->assertEquals( 'JSON Name', $new_data['first_name'] ); 872 $this->assertEquals( 'New Last', $new_data['last_name'] ); 873 $user = get_userdata( $user_id ); 874 $this->assertEquals( 'JSON Name', $user->first_name ); 875 $this->assertEquals( 'New Last', $user->last_name ); 876 877 // Check that we haven't inadvertently changed the user's password, 878 // as per https://core.trac.wordpress.org/ticket/21429 879 $this->assertEquals( $pw_before, $user->user_pass ); 880 } 881 882 public function test_update_user_role() { 883 $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); 884 885 wp_set_current_user( $this->user ); 886 $this->allow_user_to_manage_multisite(); 887 888 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $user_id ) ); 889 $request->set_param( 'roles', array( 'editor' ) ); 890 $response = $this->server->dispatch( $request ); 891 892 $new_data = $response->get_data(); 893 894 $this->assertEquals( 'editor', $new_data['roles'][0] ); 895 $this->assertNotEquals( 'administrator', $new_data['roles'][0] ); 896 897 $user = get_userdata( $user_id ); 898 $this->assertArrayHasKey( 'editor', $user->caps ); 899 $this->assertArrayNotHasKey( 'administrator', $user->caps ); 900 } 901 902 public function test_update_user_role_invalid_privilege_escalation() { 903 wp_set_current_user( $this->editor ); 904 905 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $this->editor ) ); 906 $request->set_param( 'roles', array( 'administrator' ) ); 907 $response = $this->server->dispatch( $request ); 908 909 $this->assertErrorResponse( 'rest_cannot_edit_roles', $response, 403 ); 910 $user = get_userdata( $this->editor ); 911 $this->assertArrayHasKey( 'editor', $user->caps ); 912 $this->assertArrayNotHasKey( 'administrator', $user->caps ); 913 } 914 915 public function test_update_user_role_invalid_privilege_deescalation() { 916 if ( is_multisite() ) { 917 return $this->markTestSkipped( 'Test only intended for single site.' ); 918 } 919 920 $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); 921 922 wp_set_current_user( $user_id ); 923 924 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $user_id ) ); 925 $request->set_param( 'roles', array( 'editor' ) ); 926 $response = $this->server->dispatch( $request ); 927 928 $this->assertErrorResponse( 'rest_user_invalid_role', $response, 403 ); 929 930 $user = get_userdata( $user_id ); 931 $this->assertArrayHasKey( 'administrator', $user->caps ); 932 $this->assertArrayNotHasKey( 'editor', $user->caps ); 933 } 934 935 public function test_update_user_role_privilege_deescalation_multisite() { 936 if ( ! is_multisite() ) { 937 return $this->markTestSkipped( 'Test only intended for multisite.' ); 938 } 939 940 $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); 941 942 wp_set_current_user( $user_id ); 943 $user = wp_get_current_user(); 944 update_site_option( 'site_admins', array( $user->user_login ) ); 945 946 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $user_id ) ); 947 $request->set_param( 'roles', array( 'editor' ) ); 948 $response = $this->server->dispatch( $request ); 949 950 $new_data = $response->get_data(); 951 $this->assertEquals( 'editor', $new_data['roles'][0] ); 952 $this->assertNotEquals( 'administrator', $new_data['roles'][0] ); 953 } 954 955 956 public function test_update_user_role_invalid_role() { 957 wp_set_current_user( $this->user ); 958 $this->allow_user_to_manage_multisite(); 959 960 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $this->editor ) ); 961 $request->set_param( 'roles', array( 'BeSharp' ) ); 962 $response = $this->server->dispatch( $request ); 963 964 $this->assertErrorResponse( 'rest_user_invalid_role', $response, 400 ); 965 966 $user = get_userdata( $this->editor ); 967 $this->assertArrayHasKey( 'editor', $user->caps ); 968 $this->assertArrayNotHasKey( 'BeSharp', $user->caps ); 969 } 970 971 public function test_update_user_without_permission() { 972 wp_set_current_user( $this->editor ); 973 974 $params = array( 975 'username' => 'homersimpson', 976 'password' => 'stupidsexyflanders', 977 'email' => 'chunkylover53@aol.com', 978 ); 979 980 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $this->user ) ); 981 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 982 $request->set_body_params( $params ); 983 $response = $this->server->dispatch( $request ); 984 985 $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); 986 } 987 988 public function test_update_user_invalid_id() { 989 $this->allow_user_to_manage_multisite(); 990 wp_set_current_user( $this->user ); 991 992 $params = array( 993 'id' => '156', 994 'username' => 'lisasimpson', 995 'password' => 'DavidHasselhoff', 996 'email' => 'smartgirl63_@yahoo.com', 997 ); 998 999 $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $this->editor ) ); 1000 $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); 1001 $request->set_body_params( $params ); 1002 $response = $this->server->dispatch( $request ); 1003 1004 $this->assertErrorResponse( 'rest_user_invalid_id', $response, 404 ); 1005 } 1006 1007 public function test_delete_item() { 1008 $user_id = $this->factory->user->create( array( 'display_name' => 'Deleted User' ) ); 1009 1010 $this->allow_user_to_manage_multisite(); 1011 wp_set_current_user( $this->user ); 1012 1013 $userdata = get_userdata( $user_id ); // cache for later 1014 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/users/%d', $user_id ) ); 1015 $request['force'] = true; 1016 $response = $this->server->dispatch( $request ); 1017 1018 $this->assertEquals( 200, $response->get_status() ); 1019 $data = $response->get_data(); 1020 $this->assertEquals( 'Deleted User', $data['name'] ); 1021 } 1022 1023 public function test_delete_item_no_trash() { 1024 $user_id = $this->factory->user->create( array( 'display_name' => 'Deleted User' ) ); 1025 1026 $this->allow_user_to_manage_multisite(); 1027 wp_set_current_user( $this->user ); 1028 1029 $userdata = get_userdata( $user_id ); // cache for later 1030 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/users/%d', $user_id ) ); 1031 $response = $this->server->dispatch( $request ); 1032 $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 ); 1033 1034 // Ensure the user still exists 1035 $user = get_user_by( 'id', $user_id ); 1036 $this->assertNotEmpty( $user ); 1037 } 1038 1039 public function test_delete_user_without_permission() { 1040 $user_id = $this->factory->user->create(); 1041 1042 $this->allow_user_to_manage_multisite(); 1043 wp_set_current_user( $this->editor ); 1044 1045 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/users/%d', $user_id ) ); 1046 $request['force'] = true; 1047 $response = $this->server->dispatch( $request ); 1048 1049 $this->assertErrorResponse( 'rest_user_cannot_delete', $response, 403 ); 1050 } 1051 1052 public function test_delete_user_invalid_id() { 1053 $this->allow_user_to_manage_multisite(); 1054 wp_set_current_user( $this->user ); 1055 1056 $request = new WP_REST_Request( 'DELETE', '/wp/v2/users/100' ); 1057 $request['force'] = true; 1058 $response = $this->server->dispatch( $request ); 1059 1060 $this->assertErrorResponse( 'rest_user_invalid_id', $response, 404 ); 1061 } 1062 1063 public function test_delete_user_reassign() { 1064 $this->allow_user_to_manage_multisite(); 1065 1066 // Test with a new user, to avoid any complications 1067 $user_id = $this->factory->user->create(); 1068 $reassign_id = $this->factory->user->create(); 1069 $test_post = $this->factory->post->create(array( 1070 'post_author' => $user_id, 1071 )); 1072 1073 // Sanity check to ensure the factory created the post correctly 1074 $post = get_post( $test_post ); 1075 $this->assertEquals( $user_id, $post->post_author ); 1076 1077 // Delete our test user, and reassign to the new author 1078 wp_set_current_user( $this->user ); 1079 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/users/%d', $user_id ) ); 1080 $request['force'] = true; 1081 $request->set_param( 'reassign', $reassign_id ); 1082 $response = $this->server->dispatch( $request ); 1083 1084 $this->assertEquals( 200, $response->get_status() ); 1085 1086 // Check that the post has been updated correctly 1087 $post = get_post( $test_post ); 1088 $this->assertEquals( $reassign_id, $post->post_author ); 1089 } 1090 1091 public function test_delete_user_invalid_reassign_id() { 1092 $user_id = $this->factory->user->create(); 1093 1094 $this->allow_user_to_manage_multisite(); 1095 wp_set_current_user( $this->user ); 1096 1097 $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/users/%d', $user_id ) ); 1098 $request['force'] = true; 1099 $request->set_param( 'reassign', 100 ); 1100 $response = $this->server->dispatch( $request ); 1101 1102 $this->assertErrorResponse( 'rest_user_invalid_reassign', $response, 400 ); 1103 } 1104 1105 public function test_get_item_schema() { 1106 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/users' ); 1107 $response = $this->server->dispatch( $request ); 1108 $data = $response->get_data(); 1109 $properties = $data['schema']['properties']; 1110 1111 $this->assertEquals( 18, count( $properties ) ); 1112 $this->assertArrayHasKey( 'avatar_urls', $properties ); 1113 $this->assertArrayHasKey( 'capabilities', $properties ); 1114 $this->assertArrayHasKey( 'description', $properties ); 1115 $this->assertArrayHasKey( 'email', $properties ); 1116 $this->assertArrayHasKey( 'extra_capabilities', $properties ); 1117 $this->assertArrayHasKey( 'first_name', $properties ); 1118 $this->assertArrayHasKey( 'id', $properties ); 1119 $this->assertArrayHasKey( 'last_name', $properties ); 1120 $this->assertArrayHasKey( 'link', $properties ); 1121 $this->assertArrayHasKey( 'meta', $properties ); 1122 $this->assertArrayHasKey( 'name', $properties ); 1123 $this->assertArrayHasKey( 'nickname', $properties ); 1124 $this->assertArrayHasKey( 'registered_date', $properties ); 1125 $this->assertArrayHasKey( 'slug', $properties ); 1126 $this->assertArrayHasKey( 'password', $properties ); 1127 $this->assertArrayHasKey( 'url', $properties ); 1128 $this->assertArrayHasKey( 'username', $properties ); 1129 $this->assertArrayHasKey( 'roles', $properties ); 1130 1131 } 1132 1133 public function test_get_item_schema_show_avatar() { 1134 update_option( 'show_avatars', false ); 1135 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/users' ); 1136 $response = $this->server->dispatch( $request ); 1137 $data = $response->get_data(); 1138 $properties = $data['schema']['properties']; 1139 1140 $this->assertArrayNotHasKey( 'avatar_urls', $properties ); 1141 } 1142 1143 public function test_get_additional_field_registration() { 1144 1145 $schema = array( 1146 'type' => 'integer', 1147 'description' => 'Some integer of mine', 1148 'enum' => array( 1, 2, 3, 4 ), 1149 'context' => array( 'embed', 'view', 'edit' ), 1150 ); 1151 1152 register_rest_field( 'user', 'my_custom_int', array( 1153 'schema' => $schema, 1154 'get_callback' => array( $this, 'additional_field_get_callback' ), 1155 'update_callback' => array( $this, 'additional_field_update_callback' ), 1156 ) ); 1157 1158 $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/users' ); 1159 1160 $response = $this->server->dispatch( $request ); 1161 $data = $response->get_data(); 1162 1163 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); 1164 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); 1165 1166 wp_set_current_user( 1 ); 1167 if ( is_multisite() ) { 1168 $current_user = wp_get_current_user( 1 ); 1169 update_site_option( 'site_admins', array( $current_user->user_login ) ); 1170 } 1171 1172 $request = new WP_REST_Request( 'GET', '/wp/v2/users/1' ); 1173 1174 $response = $this->server->dispatch( $request ); 1175 $this->assertArrayHasKey( 'my_custom_int', $response->data ); 1176 1177 $request = new WP_REST_Request( 'POST', '/wp/v2/users/1' ); 1178 $request->set_body_params(array( 1179 'my_custom_int' => 123, 1180 )); 1181 1182 $response = $this->server->dispatch( $request ); 1183 $this->assertEquals( 123, get_user_meta( 1, 'my_custom_int', true ) ); 1184 1185 $request = new WP_REST_Request( 'POST', '/wp/v2/users' ); 1186 $request->set_body_params(array( 1187 'my_custom_int' => 123, 1188 'email' => 'joe@foobar.com', 1189 'username' => 'abc123', 1190 'password' => 'hello', 1191 )); 1192 1193 $response = $this->server->dispatch( $request ); 1194 1195 $this->assertEquals( 123, $response->data['my_custom_int'] ); 1196 1197 global $wp_rest_additional_fields; 1198 $wp_rest_additional_fields = array(); 1199 } 1200 1201 public function test_additional_field_update_errors() { 1202 $schema = array( 1203 'type' => 'integer', 1204 'description' => 'Some integer of mine', 1205 'enum' => array( 1, 2, 3, 4 ), 1206 'context' => array( 'view', 'edit' ), 1207 ); 1208 1209 register_rest_field( 'user', 'my_custom_int', array( 1210 'schema' => $schema, 1211 'get_callback' => array( $this, 'additional_field_get_callback' ), 1212 'update_callback' => array( $this, 'additional_field_update_callback' ), 1213 ) ); 1214 1215 wp_set_current_user( 1 ); 1216 if ( is_multisite() ) { 1217 $current_user = wp_get_current_user( 1 ); 1218 update_site_option( 'site_admins', array( $current_user->user_login ) ); 1219 } 1220 1221 // Check for error on update. 1222 $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/users/%d', $this->user ) ); 1223 $request->set_body_params( array( 1224 'my_custom_int' => 'returnError', 1225 ) ); 1226 1227 $response = $this->server->dispatch( $request ); 1228 1229 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 1230 1231 global $wp_rest_additional_fields; 1232 $wp_rest_additional_fields = array(); 1233 } 1234 1235 public function additional_field_get_callback( $object ) { 1236 return get_user_meta( $object['id'], 'my_custom_int', true ); 1237 } 1238 1239 public function additional_field_update_callback( $value, $user ) { 1240 if ( 'returnError' === $value ) { 1241 return new WP_Error( 'rest_invalid_param', 'Testing an error.', array( 'status' => 400 ) ); 1242 } 1243 update_user_meta( $user->ID, 'my_custom_int', $value ); 1244 } 1245 1246 public function tearDown() { 1247 parent::tearDown(); 1248 } 1249 1250 protected function check_user_data( $user, $data, $context, $links ) { 1251 $this->assertEquals( $user->ID, $data['id'] ); 1252 $this->assertEquals( $user->display_name, $data['name'] ); 1253 $this->assertEquals( $user->user_url, $data['url'] ); 1254 $this->assertEquals( $user->description, $data['description'] ); 1255 $this->assertEquals( get_author_posts_url( $user->ID ), $data['link'] ); 1256 $this->assertArrayHasKey( 'avatar_urls', $data ); 1257 $this->assertEquals( $user->user_nicename, $data['slug'] ); 1258 1259 if ( 'edit' === $context ) { 1260 $this->assertEquals( $user->first_name, $data['first_name'] ); 1261 $this->assertEquals( $user->last_name, $data['last_name'] ); 1262 $this->assertEquals( $user->nickname, $data['nickname'] ); 1263 $this->assertEquals( $user->user_email, $data['email'] ); 1264 $this->assertEquals( (object) $user->allcaps, $data['capabilities'] ); 1265 $this->assertEquals( (object) $user->caps, $data['extra_capabilities'] ); 1266 $this->assertEquals( date( 'c', strtotime( $user->user_registered ) ), $data['registered_date'] ); 1267 $this->assertEquals( $user->user_login, $data['username'] ); 1268 $this->assertEquals( $user->roles, $data['roles'] ); 1269 } 1270 1271 if ( 'edit' !== $context ) { 1272 $this->assertArrayNotHasKey( 'roles', $data ); 1273 $this->assertArrayNotHasKey( 'capabilities', $data ); 1274 $this->assertArrayNotHasKey( 'registered', $data ); 1275 $this->assertArrayNotHasKey( 'first_name', $data ); 1276 $this->assertArrayNotHasKey( 'last_name', $data ); 1277 $this->assertArrayNotHasKey( 'nickname', $data ); 1278 $this->assertArrayNotHasKey( 'extra_capabilities', $data ); 1279 $this->assertArrayNotHasKey( 'username', $data ); 1280 } 1281 1282 $this->assertEqualSets( array( 1283 'self', 1284 'collection', 1285 ), array_keys( $links ) ); 1286 1287 $this->assertArrayNotHasKey( 'password', $data ); 1288 } 1289 1290 protected function check_get_user_response( $response, $context = 'view' ) { 1291 $this->assertEquals( 200, $response->get_status() ); 1292 1293 $data = $response->get_data(); 1294 $userdata = get_userdata( $data['id'] ); 1295 $this->check_user_data( $userdata, $data, $context, $response->get_links() ); 1296 } 1297 1298 protected function check_add_edit_user_response( $response, $update = false ) { 1299 if ( $update ) { 1300 $this->assertEquals( 200, $response->get_status() ); 1301 } else { 1302 $this->assertEquals( 201, $response->get_status() ); 1303 } 1304 1305 $data = $response->get_data(); 1306 $userdata = get_userdata( $data['id'] ); 1307 $this->check_user_data( $userdata, $data, 'edit', $response->get_links() ); 1308 } 1309 1310 protected function allow_user_to_manage_multisite() { 1311 wp_set_current_user( $this->user ); 1312 $user = wp_get_current_user(); 1313 1314 if ( is_multisite() ) { 1315 update_site_option( 'site_admins', array( $user->user_login ) ); 1316 } 1317 1318 return; 1319 } 1320 } -
tests/phpunit/tests/rest-api.php
26 26 $this->assertTrue( class_exists( 'WP_REST_Server' ) ); 27 27 $this->assertTrue( class_exists( 'WP_REST_Request' ) ); 28 28 $this->assertTrue( class_exists( 'WP_REST_Response' ) ); 29 $this->assertTrue( class_exists( 'WP_REST_Posts_Controller' ) ); 29 30 } 30 31 31 32 /** … … 36 37 $this->assertEquals( 10, has_action( 'init', 'rest_api_init' ) ); 37 38 } 38 39 40 public function test_add_extra_api_taxonomy_arguments() { 41 $taxonomy = get_taxonomy( 'category' ); 42 $this->assertTrue( $taxonomy->show_in_rest ); 43 $this->assertEquals( 'categories', $taxonomy->rest_base ); 44 $this->assertEquals( 'WP_REST_Terms_Controller', $taxonomy->rest_controller_class ); 45 46 $taxonomy = get_taxonomy( 'post_tag' ); 47 $this->assertTrue( $taxonomy->show_in_rest ); 48 $this->assertEquals( 'tags', $taxonomy->rest_base ); 49 $this->assertEquals( 'WP_REST_Terms_Controller', $taxonomy->rest_controller_class ); 50 } 51 52 public function test_add_extra_api_post_type_arguments() { 53 $post_type = get_post_type_object( 'post' ); 54 $this->assertTrue( $post_type->show_in_rest ); 55 $this->assertEquals( 'posts', $post_type->rest_base ); 56 $this->assertEquals( 'WP_REST_Posts_Controller', $post_type->rest_controller_class ); 57 58 $post_type = get_post_type_object( 'page' ); 59 $this->assertTrue( $post_type->show_in_rest ); 60 $this->assertEquals( 'pages', $post_type->rest_base ); 61 $this->assertEquals( 'WP_REST_Posts_Controller', $post_type->rest_controller_class ); 62 63 $post_type = get_post_type_object( 'attachment' ); 64 $this->assertTrue( $post_type->show_in_rest ); 65 $this->assertEquals( 'media', $post_type->rest_base ); 66 $this->assertEquals( 'WP_REST_Attachments_Controller', $post_type->rest_controller_class ); 67 } 68 39 69 /** 40 70 * Check that a single route is canonicalized. 41 71 *