Version Description
Download this release
Release Info
Developer | rmccue |
Plugin | WordPress REST API (Version 2) |
Version | 2.0-beta3 |
Comparing to | |
See all releases |
Version 2.0-beta3
- CHANGELOG.md +2545 -0
- CONTRIBUTING.md +31 -0
- README.md +80 -0
- compatibility-v1.php +110 -0
- docs/README.md +26 -0
- docs/routes/routes.md +1569 -0
- extras.php +304 -0
- lib/endpoints/class-wp-rest-attachments-controller.php +363 -0
- lib/endpoints/class-wp-rest-comments-controller.php +1069 -0
- lib/endpoints/class-wp-rest-controller.php +505 -0
- lib/endpoints/class-wp-rest-meta-controller.php +432 -0
- lib/endpoints/class-wp-rest-meta-posts-controller.php +115 -0
- lib/endpoints/class-wp-rest-post-statuses-controller.php +153 -0
- lib/endpoints/class-wp-rest-post-types-controller.php +130 -0
- lib/endpoints/class-wp-rest-posts-controller.php +1454 -0
- lib/endpoints/class-wp-rest-posts-terms-controller.php +287 -0
- lib/endpoints/class-wp-rest-revisions-controller.php +339 -0
- lib/endpoints/class-wp-rest-taxonomies-controller.php +164 -0
- lib/endpoints/class-wp-rest-terms-controller.php +595 -0
- lib/endpoints/class-wp-rest-users-controller.php +751 -0
- lib/infrastructure/class-jsonserializable.php +18 -0
- lib/infrastructure/class-wp-http-response.php +112 -0
- lib/infrastructure/class-wp-http-responseinterface.php +35 -0
- lib/infrastructure/class-wp-rest-request.php +764 -0
- lib/infrastructure/class-wp-rest-response.php +176 -0
- lib/infrastructure/class-wp-rest-server.php +962 -0
- license.txt +281 -0
- plugin.php +707 -0
- readme.txt +43 -0
- wp-api.js +987 -0
CHANGELOG.md
ADDED
@@ -0,0 +1,2545 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Changelog
|
2 |
+
|
3 |
+
## 2.0 Beta 3.0
|
4 |
+
|
5 |
+
- Add ability to declare sanitization and default options for schema fields.
|
6 |
+
|
7 |
+
The `arg_options` array can be used to declare the sanitization callback,
|
8 |
+
default value, or requirement of a field.
|
9 |
+
|
10 |
+
(props @joehoyle, [#1345][gh-1345])
|
11 |
+
(props @joehoyle, [#1346][gh-1346])
|
12 |
+
|
13 |
+
- Expand supported parameters for creating and updating Comments.
|
14 |
+
|
15 |
+
(props @rachelbaker, [#1245][gh-1245])
|
16 |
+
|
17 |
+
- Declare collection parameters for Terms of a Post.
|
18 |
+
|
19 |
+
Define the available collection parameters in `get_collection_params()` and
|
20 |
+
allow Terms of a Post to be queried by term order.
|
21 |
+
|
22 |
+
(props @danielbachhuber, [#1332][gh-1332])
|
23 |
+
|
24 |
+
- Improve the Attachment error message for an invalid Content-Disposition
|
25 |
+
|
26 |
+
(props @danielbachhuber, [#1317][gh-1317])
|
27 |
+
|
28 |
+
- Return 200 status when updating Attachments, Comments, and Users.
|
29 |
+
|
30 |
+
(props @rachelbaker, [#1348][gh-1348])
|
31 |
+
|
32 |
+
- Remove unnecessary `handle_format_param()` method.
|
33 |
+
|
34 |
+
(props @danielbachhuber, [#1331][gh-1331])
|
35 |
+
|
36 |
+
- Add `author_avatar_url` field to the Comment response and schema.
|
37 |
+
|
38 |
+
(props @rachelbaker [#1327][gh-1327])
|
39 |
+
|
40 |
+
- Introduce `rest_do_request()` for making REST requests internally.
|
41 |
+
|
42 |
+
(props @danielbachhuber, [#1333][gh-1333])
|
43 |
+
|
44 |
+
- Remove unused DateTime class.
|
45 |
+
|
46 |
+
(props @rmccue, [#1314][gh-1314])
|
47 |
+
|
48 |
+
- Add inline documentation for `$wp_rest_server` global.
|
49 |
+
|
50 |
+
(props @Shelob9, [#1324][gh-1324])
|
51 |
+
|
52 |
+
[View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta2...2.0-beta3)
|
53 |
+
[gh-1245]: https://github.com/WP-API/WP-API/issues/1245
|
54 |
+
[gh-1314]: https://github.com/WP-API/WP-API/issues/1314
|
55 |
+
[gh-1317]: https://github.com/WP-API/WP-API/issues/1317
|
56 |
+
[gh-1318]: https://github.com/WP-API/WP-API/issues/1318
|
57 |
+
[gh-1324]: https://github.com/WP-API/WP-API/issues/1324
|
58 |
+
[gh-1326]: https://github.com/WP-API/WP-API/issues/1326
|
59 |
+
[gh-1327]: https://github.com/WP-API/WP-API/issues/1327
|
60 |
+
[gh-1331]: https://github.com/WP-API/WP-API/issues/1331
|
61 |
+
[gh-1332]: https://github.com/WP-API/WP-API/issues/1332
|
62 |
+
[gh-1333]: https://github.com/WP-API/WP-API/issues/1333
|
63 |
+
[gh-1345]: https://github.com/WP-API/WP-API/issues/1345
|
64 |
+
[gh-1346]: https://github.com/WP-API/WP-API/issues/1346
|
65 |
+
[gh-1347]: https://github.com/WP-API/WP-API/issues/1347
|
66 |
+
[gh-1348]: https://github.com/WP-API/WP-API/issues/1348
|
67 |
+
|
68 |
+
## 2.0 Beta 2.0
|
69 |
+
|
70 |
+
- Load the WP REST API before the main query runs.
|
71 |
+
|
72 |
+
The `rest_api_loaded` function now hooks into the `parse_request` action.
|
73 |
+
This change prevents the main query from being run on every request and
|
74 |
+
allows sites to set `WP_USE_THEMES` to `false`. Previously, the main query
|
75 |
+
was always being run (`SELECT * FROM wp_posts LIMIT 10`), even though the
|
76 |
+
result was never used and couldn't be cached.
|
77 |
+
|
78 |
+
(props @rmccue, [#1270][gh-1270])
|
79 |
+
|
80 |
+
- Register a new field on an existing WordPress object type.
|
81 |
+
|
82 |
+
Introduces `register_api_field()` to add a field to an object and
|
83 |
+
its schema.
|
84 |
+
|
85 |
+
(props @joehoyle, @rachelbaker, [#927][gh-927])
|
86 |
+
(props @joehoyle, [#1207][gh-1207])
|
87 |
+
(props @joehoyle, [#1243][gh-1243])
|
88 |
+
|
89 |
+
- Add endpoints for viewing, creating, updating, and deleting Terms for a Post.
|
90 |
+
|
91 |
+
The new `WP_REST_Posts_Terms_Controller` class controller supports routes for
|
92 |
+
Terms that belong to a Post.
|
93 |
+
|
94 |
+
(props @joehoyle, @danielbachhuber, [#1216][gh-1216])
|
95 |
+
|
96 |
+
- Add pagination headers for collection queries.
|
97 |
+
|
98 |
+
The `X-WP-Total` and `X-WP-TotalPages` are now present in terms, comments,
|
99 |
+
and users collection responses.
|
100 |
+
|
101 |
+
(props @danielbachhuber, [#1182][gh-1182])
|
102 |
+
(props @danielbachhuber, [#1191][gh-1191])
|
103 |
+
(props @danielbachhuber, @joehoyle, [#1197][gh-1197])
|
104 |
+
|
105 |
+
- List registered namespaces in the index for feature detection.
|
106 |
+
|
107 |
+
The index (`/wp-json` by default) now contains a list of the available
|
108 |
+
namespaces. This allows for simple feature detection. You can grab the index
|
109 |
+
and check namespaces for `wp/v3` or `pluginname/v2`, which indicate the
|
110 |
+
supported endpoints on the site.
|
111 |
+
|
112 |
+
(props @rmccue,, [#1283][gh-1283])
|
113 |
+
|
114 |
+
- Standardize link property relations and support embedding for all resources.
|
115 |
+
|
116 |
+
Change link properties to use IANA-registered relations. Also adds embedding
|
117 |
+
support to Attachments, Comments and Terms.
|
118 |
+
|
119 |
+
(props @rmccue, @rachelbaker, [#1284][gh-1284])
|
120 |
+
|
121 |
+
- Add support for Composer dependency management.
|
122 |
+
|
123 |
+
Allows you to recursively install/update the WP REST API inside of WordPress
|
124 |
+
plugins or themes.
|
125 |
+
|
126 |
+
(props @QWp6t, [#1157][gh-1157])
|
127 |
+
|
128 |
+
- Return full objects in the delete response.
|
129 |
+
|
130 |
+
Instead of returning a random message when deleting a Post, Comment, Term, or
|
131 |
+
User provide the original resource data.
|
132 |
+
|
133 |
+
(props @danielbachhuber, [#1253][gh-1253])
|
134 |
+
(props @danielbachhuber, [#1254][gh-1254])
|
135 |
+
(props @danielbachhuber, [#1255][gh-1255])
|
136 |
+
(props @danielbachhuber, [#1256][gh-1256])
|
137 |
+
|
138 |
+
- Return programmatically readable error messages for invalid or missing
|
139 |
+
required parameters.
|
140 |
+
|
141 |
+
(props @joehoyle, [#1175][gh-1175])
|
142 |
+
|
143 |
+
- Declare supported arguments for Comment and User collection queries.
|
144 |
+
|
145 |
+
(props @danielbachhuber, [#1211][gh-1211])
|
146 |
+
(props @danielbachhuber, [#1217][gh-1217])
|
147 |
+
|
148 |
+
- Automatically validate parameters based on Schema data.
|
149 |
+
|
150 |
+
(props @joehoyle, [#1128][gh-1128])
|
151 |
+
|
152 |
+
- Use the `show_in_rest` attributes for exposing Taxonomies.
|
153 |
+
|
154 |
+
(props @joehoyle, [#1279][gh-1279])
|
155 |
+
|
156 |
+
- Handle `parent` when creating or updating a Term.
|
157 |
+
|
158 |
+
(props @joehoyle, [#1221][gh-1221])
|
159 |
+
|
160 |
+
- Limit fields returned in `embed` context User responses.
|
161 |
+
|
162 |
+
(props @rachelbaker, [#1251][gh-1251])
|
163 |
+
|
164 |
+
- Only include `parent` in term response when tax is hierarchical.
|
165 |
+
|
166 |
+
(props @danielbachhuber, [#1189][gh-1189])
|
167 |
+
|
168 |
+
- Fix bug in creating comments if `type` was not set.
|
169 |
+
|
170 |
+
(props @rachelbaker, [#1244][gh-1244])
|
171 |
+
|
172 |
+
- Rename `post_name` field to `post_slug`.
|
173 |
+
|
174 |
+
(props @danielbachhuber, [#1235][gh-1235])
|
175 |
+
|
176 |
+
- Add check when creating a user to verify the provided role is valid.
|
177 |
+
|
178 |
+
(props @rachelbaker, [#1267][gh-1267])
|
179 |
+
|
180 |
+
- Add link properties to the Post Status response.
|
181 |
+
|
182 |
+
(props @joehoyle, [#1243][gh-1243])
|
183 |
+
|
184 |
+
- Return `0` for `parent` in Post response instead of `null`.
|
185 |
+
|
186 |
+
(props @danielbachhuber, [#1269][gh-1269])
|
187 |
+
|
188 |
+
- Only link `author` when there's a valid author
|
189 |
+
|
190 |
+
(props @danielbachhuber, [#1203][gh-1203])
|
191 |
+
|
192 |
+
- Only permit querying by parent term when tax is hierarchical.
|
193 |
+
|
194 |
+
(props @danielbachhuber, [#1219][gh-1219])
|
195 |
+
|
196 |
+
- Only permit deleting posts of the proper type
|
197 |
+
|
198 |
+
(props @danielbachhuber, [#1257][gh-1257])
|
199 |
+
|
200 |
+
- Set pagination headers even when no found posts.
|
201 |
+
|
202 |
+
(props @danielbachhuber, [#1209][gh-1209])
|
203 |
+
|
204 |
+
- Correct prefix in `rest_request_parameter_order` filter.
|
205 |
+
|
206 |
+
(props @quasel, [#1158][gh-1158])
|
207 |
+
|
208 |
+
- Retool `WP_REST_Terms_Controller` to follow Posts controller pattern.
|
209 |
+
|
210 |
+
(props @danielbachhuber, [#1170][gh-1170])
|
211 |
+
|
212 |
+
- Remove unused `accept_json argument` from the `register_routes` method.
|
213 |
+
|
214 |
+
(props @quasel, [#1160][gh-1160])
|
215 |
+
|
216 |
+
- Fix typo in `sanitize_params` inline documentation.
|
217 |
+
|
218 |
+
(props @Shelob9, [#1226][gh-1226])
|
219 |
+
|
220 |
+
- Remove commented out code in dispatch method.
|
221 |
+
|
222 |
+
(props @rachelbaker, [#1162][gh-1162])
|
223 |
+
|
224 |
+
|
225 |
+
[View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta1.1...2.0-beta2)
|
226 |
+
[gh-927]: https://github.com/WP-API/WP-API/issues/927
|
227 |
+
[gh-1128]: https://github.com/WP-API/WP-API/issues/1128
|
228 |
+
[gh-1157]: https://github.com/WP-API/WP-API/issues/1157
|
229 |
+
[gh-1158]: https://github.com/WP-API/WP-API/issues/1158
|
230 |
+
[gh-1160]: https://github.com/WP-API/WP-API/issues/1160
|
231 |
+
[gh-1162]: https://github.com/WP-API/WP-API/issues/1162
|
232 |
+
[gh-1168]: https://github.com/WP-API/WP-API/issues/1168
|
233 |
+
[gh-1170]: https://github.com/WP-API/WP-API/issues/1170
|
234 |
+
[gh-1171]: https://github.com/WP-API/WP-API/issues/1171
|
235 |
+
[gh-1175]: https://github.com/WP-API/WP-API/issues/1175
|
236 |
+
[gh-1176]: https://github.com/WP-API/WP-API/issues/1176
|
237 |
+
[gh-1177]: https://github.com/WP-API/WP-API/issues/1177
|
238 |
+
[gh-1181]: https://github.com/WP-API/WP-API/issues/1181
|
239 |
+
[gh-1182]: https://github.com/WP-API/WP-API/issues/1182
|
240 |
+
[gh-1188]: https://github.com/WP-API/WP-API/issues/1188
|
241 |
+
[gh-1189]: https://github.com/WP-API/WP-API/issues/1189
|
242 |
+
[gh-1191]: https://github.com/WP-API/WP-API/issues/1191
|
243 |
+
[gh-1197]: https://github.com/WP-API/WP-API/issues/1197
|
244 |
+
[gh-1200]: https://github.com/WP-API/WP-API/issues/1200
|
245 |
+
[gh-1203]: https://github.com/WP-API/WP-API/issues/1203
|
246 |
+
[gh-1207]: https://github.com/WP-API/WP-API/issues/1207
|
247 |
+
[gh-1209]: https://github.com/WP-API/WP-API/issues/1209
|
248 |
+
[gh-1210]: https://github.com/WP-API/WP-API/issues/1210
|
249 |
+
[gh-1211]: https://github.com/WP-API/WP-API/issues/1211
|
250 |
+
[gh-1216]: https://github.com/WP-API/WP-API/issues/1216
|
251 |
+
[gh-1217]: https://github.com/WP-API/WP-API/issues/1217
|
252 |
+
[gh-1219]: https://github.com/WP-API/WP-API/issues/1219
|
253 |
+
[gh-1221]: https://github.com/WP-API/WP-API/issues/1221
|
254 |
+
[gh-1226]: https://github.com/WP-API/WP-API/issues/1226
|
255 |
+
[gh-1235]: https://github.com/WP-API/WP-API/issues/1235
|
256 |
+
[gh-1243]: https://github.com/WP-API/WP-API/issues/1243
|
257 |
+
[gh-1244]: https://github.com/WP-API/WP-API/issues/1244
|
258 |
+
[gh-1249]: https://github.com/WP-API/WP-API/issues/1249
|
259 |
+
[gh-1251]: https://github.com/WP-API/WP-API/issues/1251
|
260 |
+
[gh-1253]: https://github.com/WP-API/WP-API/issues/1253
|
261 |
+
[gh-1254]: https://github.com/WP-API/WP-API/issues/1254
|
262 |
+
[gh-1255]: https://github.com/WP-API/WP-API/issues/1255
|
263 |
+
[gh-1256]: https://github.com/WP-API/WP-API/issues/1256
|
264 |
+
[gh-1257]: https://github.com/WP-API/WP-API/issues/1257
|
265 |
+
[gh-1259]: https://github.com/WP-API/WP-API/issues/1259
|
266 |
+
[gh-1267]: https://github.com/WP-API/WP-API/issues/1267
|
267 |
+
[gh-1268]: https://github.com/WP-API/WP-API/issues/1268
|
268 |
+
[gh-1269]: https://github.com/WP-API/WP-API/issues/1269
|
269 |
+
[gh-1270]: https://github.com/WP-API/WP-API/issues/1270
|
270 |
+
[gh-1276]: https://github.com/WP-API/WP-API/issues/1276
|
271 |
+
[gh-1277]: https://github.com/WP-API/WP-API/issues/1277
|
272 |
+
[gh-1279]: https://github.com/WP-API/WP-API/issues/1279
|
273 |
+
[gh-1283]: https://github.com/WP-API/WP-API/issues/1283
|
274 |
+
[gh-1284]: https://github.com/WP-API/WP-API/issues/1284
|
275 |
+
[gh-1295]: https://github.com/WP-API/WP-API/issues/1295
|
276 |
+
[gh-1301]: https://github.com/WP-API/WP-API/issues/1301
|
277 |
+
|
278 |
+
|
279 |
+
## 2.0 Beta 1.1
|
280 |
+
|
281 |
+
- Fix user access security vulnerability.
|
282 |
+
|
283 |
+
Authenticated users were able to escalate their privileges bypassing the
|
284 |
+
expected capabilities check.
|
285 |
+
|
286 |
+
Reported by @kacperszurek on 2015-05-16.
|
287 |
+
|
288 |
+
|
289 |
+
## 2.0 Beta 1
|
290 |
+
|
291 |
+
- Avoid passing server to the controller each time
|
292 |
+
|
293 |
+
(props @rmccue, [#543][gh-543])
|
294 |
+
|
295 |
+
- Unify naming of methods across classes
|
296 |
+
|
297 |
+
(props @danielbachhuber, [#546][gh-546])
|
298 |
+
|
299 |
+
- Disable unit tests while we move things around
|
300 |
+
|
301 |
+
(props @danielbachhuber, [#548][gh-548])
|
302 |
+
|
303 |
+
- Mock code to represent new Resources
|
304 |
+
|
305 |
+
(props @danielbachhuber, [#549][gh-549])
|
306 |
+
|
307 |
+
- WP_JSON_Controller POC
|
308 |
+
|
309 |
+
(props @danielbachhuber, [#556][gh-556])
|
310 |
+
|
311 |
+
- Add request object
|
312 |
+
|
313 |
+
(props @rmccue, [#563][gh-563])
|
314 |
+
|
315 |
+
- Update routes for new-style registration
|
316 |
+
|
317 |
+
(props @rmccue, [#564][gh-564])
|
318 |
+
|
319 |
+
- Add compatibility with v1 routing
|
320 |
+
|
321 |
+
(props @rmccue, [#565][gh-565])
|
322 |
+
|
323 |
+
- Remove Last-Modified and If-Unmodified-Since
|
324 |
+
|
325 |
+
(props @rmccue, [#566][gh-566])
|
326 |
+
|
327 |
+
- Allow multiple route registration
|
328 |
+
|
329 |
+
(props @rmccue, [#586][gh-586])
|
330 |
+
|
331 |
+
- Use https in test setup
|
332 |
+
|
333 |
+
(props @danielbachhuber, [#588][gh-588])
|
334 |
+
|
335 |
+
- Terms Controller Redux
|
336 |
+
|
337 |
+
(props @danielbachhuber, [#579][gh-579])
|
338 |
+
|
339 |
+
- Add hypermedia functionality to the response
|
340 |
+
|
341 |
+
(props @rmccue, @rachelbaker, [#570][gh-570])
|
342 |
+
|
343 |
+
- Initial pass at new style Users Controller
|
344 |
+
|
345 |
+
(props @rachelbaker, [#603][gh-603])
|
346 |
+
|
347 |
+
- Drop old Users class
|
348 |
+
|
349 |
+
(props @danielbachhuber, [#619][gh-619])
|
350 |
+
|
351 |
+
- Fix passing array to 'methods' are in register_json_route()
|
352 |
+
|
353 |
+
(props @joehoyle, [#620][gh-620])
|
354 |
+
|
355 |
+
- Allow 'ignore_sticky_posts' filter #415
|
356 |
+
|
357 |
+
(props @Shelob9, [#612][gh-612], [#415][gh-415])
|
358 |
+
|
359 |
+
- Initial Extras.php commit
|
360 |
+
|
361 |
+
(props @NikV, [#575][gh-575])
|
362 |
+
|
363 |
+
- Allow filtering response before returning
|
364 |
+
|
365 |
+
(props @danielbachhuber, [#573][gh-573])
|
366 |
+
|
367 |
+
- Parse JSON data from the request
|
368 |
+
|
369 |
+
(props @rmccue, [#626][gh-626])
|
370 |
+
|
371 |
+
- Remove old taxonomies controller
|
372 |
+
|
373 |
+
(props @danielbachhuber, [#637][gh-637])
|
374 |
+
|
375 |
+
- Make our code DRY by consolidating use of strtoupper
|
376 |
+
|
377 |
+
(props @danielbachhuber, [#589][gh-589])
|
378 |
+
|
379 |
+
- Move WP_Test_JSON_Testcase to a properly named file
|
380 |
+
|
381 |
+
(props @danielbachhuber, [#643][gh-643])
|
382 |
+
|
383 |
+
- Speed up builds by only running against MS once
|
384 |
+
|
385 |
+
(props @danielbachhuber, [#638][gh-638])
|
386 |
+
|
387 |
+
- `->prepare_post()` should be public
|
388 |
+
|
389 |
+
(props @staylor, [#645][gh-645])
|
390 |
+
|
391 |
+
- Get by and return `term_taxonomy_id`
|
392 |
+
|
393 |
+
(props @danielbachhuber, [#648][gh-648])
|
394 |
+
|
395 |
+
- Base class with standard test methods for every controller
|
396 |
+
|
397 |
+
(props @danielbachhuber, [#649][gh-649])
|
398 |
+
|
399 |
+
- Unused arguments
|
400 |
+
|
401 |
+
(props @staylor, [#647][gh-647])
|
402 |
+
|
403 |
+
- JS should be under version control
|
404 |
+
|
405 |
+
(props @staylor, [#644][gh-644])
|
406 |
+
|
407 |
+
- Register multiple routes for users correctly
|
408 |
+
|
409 |
+
(props @rmccue, [#654][gh-654])
|
410 |
+
|
411 |
+
- Check get_post_type_object() returns an object before using it
|
412 |
+
|
413 |
+
(props @NateWr, [#656][gh-656])
|
414 |
+
|
415 |
+
- Run multisite test against PHP 5.2
|
416 |
+
|
417 |
+
(props @danielbachhuber, [#659][gh-659])
|
418 |
+
|
419 |
+
- Pass the edit context when returning the create or update response. Fixes
|
420 |
+
#661
|
421 |
+
|
422 |
+
(props @rachelbaker, [#664][gh-664], [#661][gh-661])
|
423 |
+
|
424 |
+
- Check for errors when responding to create
|
425 |
+
|
426 |
+
(props @rmccue, [#652][gh-652])
|
427 |
+
|
428 |
+
- Fix bug in check_required_parameters where JSON params were missed
|
429 |
+
|
430 |
+
(props @rachelbaker, [#673][gh-673])
|
431 |
+
|
432 |
+
- Fix parameter handling and improve Users Controller tests
|
433 |
+
|
434 |
+
(props @rachelbaker, [#675][gh-675])
|
435 |
+
|
436 |
+
- Check that param is null
|
437 |
+
|
438 |
+
(props @danielbachhuber, [#678][gh-678])
|
439 |
+
|
440 |
+
- Parse URL-encoded body with PUT requests
|
441 |
+
|
442 |
+
(props @rmccue, [#681][gh-681])
|
443 |
+
|
444 |
+
- End to end testing for users
|
445 |
+
|
446 |
+
(props @rmccue, [#682][gh-682])
|
447 |
+
|
448 |
+
- End to end test coverage of Terms Controller
|
449 |
+
|
450 |
+
(props @danielbachhuber, @rmccue, [#676][gh-676])
|
451 |
+
|
452 |
+
- Add ability to wrap response in an envelope
|
453 |
+
|
454 |
+
(props @Japh, @rmccue, [#628][gh-628])
|
455 |
+
|
456 |
+
- Wrap up PUT handling in Users Controller
|
457 |
+
|
458 |
+
(props @rachelbaker, [#683][gh-683])
|
459 |
+
|
460 |
+
- ID shouldn't be a param on update user endpoint
|
461 |
+
|
462 |
+
(props @joehoyle, [#692][gh-692])
|
463 |
+
|
464 |
+
- Clean up Terms controller
|
465 |
+
|
466 |
+
(props @danielbachhuber, [#696][gh-696])
|
467 |
+
|
468 |
+
- Remove mis-placed duplicate Users Delete route and id parameter
|
469 |
+
|
470 |
+
(props @rachelbaker, [#700][gh-700])
|
471 |
+
|
472 |
+
- Fields cleanup for User controller
|
473 |
+
|
474 |
+
(props @danielbachhuber, [#701][gh-701])
|
475 |
+
|
476 |
+
- Throw an error when a user tries to update to an existing user's email
|
477 |
+
|
478 |
+
(props @danielbachhuber, [#705][gh-705])
|
479 |
+
|
480 |
+
- `PUT User` shouldn't permit using existing `user_login` or `user_nicename`
|
481 |
+
|
482 |
+
(props @danielbachhuber, [#707][gh-707])
|
483 |
+
|
484 |
+
- Change return value of WP_JSON_Users_Controller::get_item.
|
485 |
+
|
486 |
+
(props @rachelbaker, [#712][gh-712])
|
487 |
+
|
488 |
+
- Add the ability to specify default param values in register_json_route
|
489 |
+
|
490 |
+
(props @WP-API, [#715][gh-715])
|
491 |
+
|
492 |
+
- Merge JS into main repo
|
493 |
+
|
494 |
+
(props @tlovett1, [#730][gh-730])
|
495 |
+
|
496 |
+
- Make the "required" param on args optional
|
497 |
+
|
498 |
+
(props @joehoyle, @rachelbaker, [#728][gh-728])
|
499 |
+
|
500 |
+
- Always allow JSON data for POST and PUT requests
|
501 |
+
|
502 |
+
(props @rachelbaker, [#731][gh-731])
|
503 |
+
|
504 |
+
- Initial pass at new style Posts Controller
|
505 |
+
|
506 |
+
(props @rachelbaker, [#684][gh-684])
|
507 |
+
|
508 |
+
- Drop required argument declaration
|
509 |
+
|
510 |
+
(props @danielbachhuber, [#736][gh-736])
|
511 |
+
|
512 |
+
- Update post format after post has been updated
|
513 |
+
|
514 |
+
(props @danielbachhuber, [#737][gh-737])
|
515 |
+
|
516 |
+
- Allow the title to be set via title.raw
|
517 |
+
|
518 |
+
(props @iseulde, [#741][gh-741])
|
519 |
+
|
520 |
+
- Fix some incompatible interfaces
|
521 |
+
|
522 |
+
(props @staylor, [#742][gh-742])
|
523 |
+
|
524 |
+
- Full Test Coverage for Users Controller
|
525 |
+
|
526 |
+
(props @rachelbaker, [#744][gh-744])
|
527 |
+
|
528 |
+
- Refer to BaseCollection statically instead of via this.constructor
|
529 |
+
|
530 |
+
(props @tlovett1, [#750][gh-750])
|
531 |
+
|
532 |
+
- Adjustments to Users Controller DocBlocks
|
533 |
+
|
534 |
+
(props @rachelbaker, [#743][gh-743])
|
535 |
+
|
536 |
+
- Default `args` to an empty array
|
537 |
+
|
538 |
+
(props @danielbachhuber, [#758][gh-758])
|
539 |
+
|
540 |
+
- Do not require type parameter to be set when updating a Post
|
541 |
+
|
542 |
+
(props @rachelbaker, [#761][gh-761])
|
543 |
+
|
544 |
+
- Remove from docs the "post_type" filter parameter for /posts endpoint
|
545 |
+
|
546 |
+
(props @NateWr, [#666][gh-666])
|
547 |
+
|
548 |
+
- Resolve regressions in Posts Controller
|
549 |
+
|
550 |
+
(props @rachelbaker, [#753][gh-753])
|
551 |
+
|
552 |
+
- WP_Json_Server::dispatch() should always return a WP_JSON_Response
|
553 |
+
|
554 |
+
(props @joehoyle, [#714][gh-714])
|
555 |
+
|
556 |
+
- Update Timeline note
|
557 |
+
|
558 |
+
(props @tapsboy, [#774][gh-774])
|
559 |
+
|
560 |
+
- Make json_pre_dispatch and json_post_dispatch consistent
|
561 |
+
|
562 |
+
(props @joehoyle, [#786][gh-786])
|
563 |
+
|
564 |
+
- Normalize our test classes setUP and tearDown methods
|
565 |
+
|
566 |
+
(props @rachelbaker, [#794][gh-794])
|
567 |
+
|
568 |
+
- Comments Endpoints
|
569 |
+
|
570 |
+
(props @joehoyle, @rachelbaker, [#693][gh-693])
|
571 |
+
|
572 |
+
- Correct /posts/ endpoint read post permission logic
|
573 |
+
|
574 |
+
(props @rachelbaker, [#805][gh-805])
|
575 |
+
|
576 |
+
- Ensure global $post has proper state when the json_prepare_post filter f...
|
577 |
+
|
578 |
+
(props @ericandrewlewis, [#823][gh-823])
|
579 |
+
|
580 |
+
- Adds missing description field to the Taxonomy response
|
581 |
+
|
582 |
+
(props @rachelbaker, [#826][gh-826])
|
583 |
+
|
584 |
+
- Posts controller abstraction
|
585 |
+
|
586 |
+
(props @danielbachhuber, [#820][gh-820])
|
587 |
+
|
588 |
+
- Remove old Pages and CustomPostType classes no longer in use
|
589 |
+
|
590 |
+
(props @danielbachhuber, [#831][gh-831])
|
591 |
+
|
592 |
+
- Add `featured_image` attribute for post types that support `thumbnails`
|
593 |
+
|
594 |
+
(props @danielbachhuber, [#832][gh-832])
|
595 |
+
|
596 |
+
- Specify Capability in Route
|
597 |
+
|
598 |
+
(props @joehoyle, [#602][gh-602])
|
599 |
+
|
600 |
+
- Posts Controller Headers and Links Fixes
|
601 |
+
|
602 |
+
(props @rachelbaker, [#836][gh-836])
|
603 |
+
|
604 |
+
- Don't noop `future` status. It's confusing
|
605 |
+
|
606 |
+
(props @danielbachhuber, [#841][gh-841])
|
607 |
+
|
608 |
+
- Remove unused $request parameter from prepare_links method.
|
609 |
+
|
610 |
+
(props @rachelbaker, [#842][gh-842])
|
611 |
+
|
612 |
+
- Expose basic author details when user has published posts
|
613 |
+
|
614 |
+
(props @danielbachhuber, [#838][gh-838])
|
615 |
+
|
616 |
+
- Make `get_post_type_base()` public so we can DRY
|
617 |
+
|
618 |
+
(props @danielbachhuber, [#845][gh-845])
|
619 |
+
|
620 |
+
- Remove Duplicate Logic for Post Type Attributes
|
621 |
+
|
622 |
+
(props @rachelbaker, [#853][gh-853])
|
623 |
+
|
624 |
+
- Move infrastructure classes to `lib/infrastructure`, part one
|
625 |
+
|
626 |
+
(props @danielbachhuber, [#872][gh-872])
|
627 |
+
|
628 |
+
- Passing a value for the slug parameter should update the post_name.
|
629 |
+
|
630 |
+
(props @rachelbaker, [#883][gh-883])
|
631 |
+
|
632 |
+
- Break Pages tests into a separate class
|
633 |
+
|
634 |
+
(props @danielbachhuber, [#870][gh-870])
|
635 |
+
|
636 |
+
- Empty checks in Posts Controller make setting values to Falsy impossible
|
637 |
+
|
638 |
+
(props @joehoyle, [#885][gh-885])
|
639 |
+
|
640 |
+
- Change project name to WP REST API in plugin name and Readme title.
|
641 |
+
|
642 |
+
(props @rachelbaker, [#876][gh-876])
|
643 |
+
|
644 |
+
- Return 200 and an empty array for valid queries with 0 results.
|
645 |
+
|
646 |
+
(props @rachelbaker, [#888][gh-888])
|
647 |
+
|
648 |
+
- Include the taxonomy in the term response
|
649 |
+
|
650 |
+
(props @danielbachhuber, [#891][gh-891])
|
651 |
+
|
652 |
+
- JSON Schemas for our Controllers, second attempt
|
653 |
+
|
654 |
+
(props @danielbachhuber, [#844][gh-844])
|
655 |
+
|
656 |
+
- From the left with love
|
657 |
+
|
658 |
+
(props @MichaelArestad, [#896][gh-896])
|
659 |
+
|
660 |
+
- Add `link` field to Users, Comments and Terms
|
661 |
+
|
662 |
+
(props @danielbachhuber, [#897][gh-897])
|
663 |
+
|
664 |
+
- Fix flipped assertions
|
665 |
+
|
666 |
+
(props @danielbachhuber, [#902][gh-902])
|
667 |
+
|
668 |
+
- Add missing break statement
|
669 |
+
|
670 |
+
(props @danielbachhuber, [#905][gh-905])
|
671 |
+
|
672 |
+
- Move all of our endpoint controllers to `lib/endpoints`
|
673 |
+
|
674 |
+
(props @danielbachhuber, [#906][gh-906])
|
675 |
+
|
676 |
+
- Always include `guid` in Post and Page schemas
|
677 |
+
|
678 |
+
(props @danielbachhuber, [#907][gh-907])
|
679 |
+
|
680 |
+
- If post type doesn't match controller post type, throw 404
|
681 |
+
|
682 |
+
(props @danielbachhuber, [#908][gh-908])
|
683 |
+
|
684 |
+
- Allow post type attributes to be set based on presence in schema
|
685 |
+
|
686 |
+
(props @danielbachhuber, [#910][gh-910])
|
687 |
+
|
688 |
+
- Updating another post field shouldn't change sticky status
|
689 |
+
|
690 |
+
(props @danielbachhuber, [#911][gh-911])
|
691 |
+
|
692 |
+
- Expose post type data at `/types`
|
693 |
+
|
694 |
+
(props @danielbachhuber, [#914][gh-914])
|
695 |
+
|
696 |
+
- Always defer to controller for post type
|
697 |
+
|
698 |
+
(props @danielbachhuber, [#913][gh-913])
|
699 |
+
|
700 |
+
- Add `template` parameter to Page response
|
701 |
+
|
702 |
+
(props @danielbachhuber, [#909][gh-909])
|
703 |
+
|
704 |
+
- Convert /media to new controller pattern
|
705 |
+
|
706 |
+
(props @danielbachhuber, [#904][gh-904])
|
707 |
+
|
708 |
+
- Remove v1.0 Posts (and Media) controller
|
709 |
+
|
710 |
+
(props @WP-API, [#923][gh-923])
|
711 |
+
|
712 |
+
- Clean up taxonomies controller tests by running through dispatch; add schema
|
713 |
+
|
714 |
+
(props @danielbachhuber, [#919][gh-919])
|
715 |
+
|
716 |
+
- Separate permissions logic for comments
|
717 |
+
|
718 |
+
(props @joehoyle, [#854][gh-854])
|
719 |
+
|
720 |
+
- `wp-json.php` isn't needed anymore
|
721 |
+
|
722 |
+
(props @danielbachhuber, [#931][gh-931])
|
723 |
+
|
724 |
+
- Tweak the post controller
|
725 |
+
|
726 |
+
(props @rmccue, [#936][gh-936])
|
727 |
+
|
728 |
+
- Switch CORS headers callback to new action
|
729 |
+
|
730 |
+
(props @rmccue, [#935][gh-935])
|
731 |
+
|
732 |
+
- Remove `_id` suffix from field names
|
733 |
+
|
734 |
+
(props @danielbachhuber, [#941][gh-941])
|
735 |
+
|
736 |
+
- Add `author_ip`, `author_user_agent` and `karma` fields to Comment
|
737 |
+
|
738 |
+
(props @danielbachhuber, [#946][gh-946])
|
739 |
+
|
740 |
+
- Explicitly test that these additional comment fields aren't present
|
741 |
+
|
742 |
+
(props @danielbachhuber, [#947][gh-947])
|
743 |
+
|
744 |
+
- Allow `title` to be set to empty string in request
|
745 |
+
|
746 |
+
(props @danielbachhuber, [#953][gh-953])
|
747 |
+
|
748 |
+
- Use real URLs instead of query_params attribute
|
749 |
+
|
750 |
+
(props @rmccue, [#958][gh-958])
|
751 |
+
|
752 |
+
- Use `wp_filter_post_kses()` instead of `wp_kses_post()` on insert
|
753 |
+
|
754 |
+
(props @danielbachhuber, [#917][gh-917])
|
755 |
+
|
756 |
+
- Add missing core path to post endpoint link hrefs.
|
757 |
+
|
758 |
+
(props @rachelbaker, [#966][gh-966])
|
759 |
+
|
760 |
+
- Allow HTTP method to be overwritten by HTTP_X_HTTP_METHOD_OVERRIDE
|
761 |
+
|
762 |
+
(props @tlovett1, [#967][gh-967])
|
763 |
+
|
764 |
+
- Fix attachment caption and description fields
|
765 |
+
|
766 |
+
(props @danielbachhuber, [#968][gh-968])
|
767 |
+
|
768 |
+
- Move validation to the `WP_JSON_Request` class
|
769 |
+
|
770 |
+
(props @danielbachhuber, [#971][gh-971])
|
771 |
+
|
772 |
+
- Move the Route Registering to the Controllers
|
773 |
+
|
774 |
+
(props @joehoyle, [#970][gh-970])
|
775 |
+
|
776 |
+
- Correct test method spelling of permission.
|
777 |
+
|
778 |
+
(props @rachelbaker, [#973][gh-973])
|
779 |
+
|
780 |
+
- Permission abstractions 2
|
781 |
+
|
782 |
+
(props @joehoyle, [#987][gh-987])
|
783 |
+
|
784 |
+
- If an invalid date is supplied to create / update post, return an error
|
785 |
+
|
786 |
+
(props @joehoyle, [#1000][gh-1000])
|
787 |
+
|
788 |
+
- Update README.md
|
789 |
+
|
790 |
+
(props @hubdotcom, [#1006][gh-1006])
|
791 |
+
|
792 |
+
- Add embeddable attachments to Post response _links
|
793 |
+
|
794 |
+
(props @rachelbaker, [#1026][gh-1026])
|
795 |
+
|
796 |
+
- Throw error if requesting user doesn't have capability for context
|
797 |
+
|
798 |
+
(props @danielbachhuber, [#1033][gh-1033])
|
799 |
+
|
800 |
+
- `/wp/statuses` endpoint, modeled after `/wp/types`
|
801 |
+
|
802 |
+
(props @danielbachhuber, [#1039][gh-1039])
|
803 |
+
|
804 |
+
- Turn post types from array to object, with name as key
|
805 |
+
|
806 |
+
(props @danielbachhuber, [#1042][gh-1042])
|
807 |
+
|
808 |
+
- Add missing response fields to the user schema.
|
809 |
+
|
810 |
+
(props @rachelbaker, [#1034][gh-1034])
|
811 |
+
|
812 |
+
- Setting a post to be sticky AND password protected should fail
|
813 |
+
|
814 |
+
(props @joehoyle, [#1044][gh-1044])
|
815 |
+
|
816 |
+
- Use appropriate functions when creating users on multisite
|
817 |
+
|
818 |
+
(props @danielbachhuber, [#1043][gh-1043])
|
819 |
+
|
820 |
+
- Define context in which each schema field appears
|
821 |
+
|
822 |
+
(props @danielbachhuber, [#1046][gh-1046])
|
823 |
+
|
824 |
+
- Use schema abstraction to limit which user fields are exposed per context
|
825 |
+
|
826 |
+
(props @danielbachhuber, [#1049][gh-1049])
|
827 |
+
|
828 |
+
- Run Statuses, Types, and Taxonomies through our context filter
|
829 |
+
|
830 |
+
(props @danielbachhuber, [#1050][gh-1050])
|
831 |
+
|
832 |
+
- Run Terms controller through schema context filter
|
833 |
+
|
834 |
+
(props @danielbachhuber, [#1051][gh-1051])
|
835 |
+
|
836 |
+
- Don't allow contributors to set sticky on posts
|
837 |
+
|
838 |
+
(props @joehoyle, [#1052][gh-1052])
|
839 |
+
|
840 |
+
- Return correct response code from wp_insert_post() error
|
841 |
+
|
842 |
+
(props @joehoyle, [#999][gh-999])
|
843 |
+
|
844 |
+
- Move the permissions checks for password and author into the permissions
|
845 |
+
callback
|
846 |
+
|
847 |
+
(props @joehoyle, [#1054][gh-1054])
|
848 |
+
|
849 |
+
- Use full Post schema to filter fields based on context
|
850 |
+
|
851 |
+
(props @danielbachhuber, [#1053][gh-1053])
|
852 |
+
|
853 |
+
- Allow WP_JSON_Server::send_header()/send_headers() to be accessed publicly
|
854 |
+
|
855 |
+
(props @johnbillion, [#1059][gh-1059])
|
856 |
+
|
857 |
+
- Remove unnecessary sticky posts abstraction
|
858 |
+
|
859 |
+
(props @danielbachhuber, [#1064][gh-1064])
|
860 |
+
|
861 |
+
- Re-enable the Post endpoint filters
|
862 |
+
|
863 |
+
(props @rachelbaker, [#1028][gh-1028])
|
864 |
+
|
865 |
+
- Fix the format of the args when building them from the Schema
|
866 |
+
|
867 |
+
(props @joehoyle, [#1066][gh-1066])
|
868 |
+
|
869 |
+
- Add more tests for the server class
|
870 |
+
|
871 |
+
(props @rmccue, [#685][gh-685])
|
872 |
+
|
873 |
+
- Fix error with OPTIONS requests
|
874 |
+
|
875 |
+
(props @rmccue, [#1091][gh-1091])
|
876 |
+
|
877 |
+
- Ensure the JSON endpoint URL is properly escaped
|
878 |
+
|
879 |
+
(props @johnbillion, [#1097][gh-1097])
|
880 |
+
|
881 |
+
- Correct a bunch of filter docs in WP_JSON_Server
|
882 |
+
|
883 |
+
(props @johnbillion, [#1098][gh-1098])
|
884 |
+
|
885 |
+
- Require `moderate_comments` capability to context=edit a Comment
|
886 |
+
|
887 |
+
(props @danielbachhuber, @joehoyle, [#951][gh-951])
|
888 |
+
|
889 |
+
- Add all the permission check functions to the base controller for better
|
890 |
+
consistancy and help to subclasses
|
891 |
+
|
892 |
+
(props @joehoyle, [#1104][gh-1104])
|
893 |
+
|
894 |
+
- `author` is the Comment attribute with user ID
|
895 |
+
|
896 |
+
(props @danielbachhuber, [#1106][gh-1106])
|
897 |
+
|
898 |
+
- Fix copy pasta in the schema checks
|
899 |
+
|
900 |
+
(props @danielbachhuber, [#1111][gh-1111])
|
901 |
+
|
902 |
+
- When `context=edit`, confirm user can `manage_comments`
|
903 |
+
|
904 |
+
(props @danielbachhuber, [#1112][gh-1112])
|
905 |
+
|
906 |
+
- Abstract revisions to dedicated controller; only include revisioned fields
|
907 |
+
|
908 |
+
(props @danielbachhuber, [#1110][gh-1110])
|
909 |
+
|
910 |
+
- Add Embeddable Taxonomy Term Links to the Post Response
|
911 |
+
|
912 |
+
(props @rachelbaker, [#1048][gh-1048])
|
913 |
+
|
914 |
+
- Increase Terms Controller test coverage
|
915 |
+
|
916 |
+
(props @rachelbaker, [#1117][gh-1117])
|
917 |
+
|
918 |
+
- Rename the `wp_json_server_before_serve` to `wp_json_init`
|
919 |
+
|
920 |
+
(props @joehoyle, [#1105][gh-1105])
|
921 |
+
|
922 |
+
- Drop revision embedding from posts controller; link instead
|
923 |
+
|
924 |
+
(props @danielbachhuber, [#1121][gh-1121])
|
925 |
+
|
926 |
+
- Add security section to our README
|
927 |
+
|
928 |
+
(props @rmccue, [#1123][gh-1123])
|
929 |
+
|
930 |
+
- Missing @param inline docs in main plugin file.
|
931 |
+
|
932 |
+
(props @Shelob9, [#1122][gh-1122])
|
933 |
+
|
934 |
+
- Ensure post deletion is idempotent
|
935 |
+
|
936 |
+
(props @rmccue, [#959][gh-959])
|
937 |
+
|
938 |
+
- Support for validation / sanitize callbacks in arguments
|
939 |
+
|
940 |
+
(props @joehoyle, [#989][gh-989])
|
941 |
+
|
942 |
+
- Display links in collections
|
943 |
+
|
944 |
+
(props @rmccue, @rachelbaker, [#937][gh-937])
|
945 |
+
|
946 |
+
- Sanitize args using new args API
|
947 |
+
|
948 |
+
(props @joehoyle, [#1129][gh-1129])
|
949 |
+
|
950 |
+
- Use the user fields from the item schema as the request args in route
|
951 |
+
registration
|
952 |
+
|
953 |
+
(props @joehoyle, [#1109][gh-1109])
|
954 |
+
|
955 |
+
- Build the array of args for /wp/posts from the allowed query vars
|
956 |
+
|
957 |
+
(props @joehoyle, [#1108][gh-1108])
|
958 |
+
|
959 |
+
- Show all the invalid param errors at once
|
960 |
+
|
961 |
+
(props @joehoyle, [#1131][gh-1131])
|
962 |
+
|
963 |
+
- Readonly attribute in schema to exclude from args array
|
964 |
+
|
965 |
+
(props @joehoyle, [#1133][gh-1133])
|
966 |
+
|
967 |
+
- Use the `required` flags from the schema for CREATE post
|
968 |
+
|
969 |
+
(props @joehoyle, [#1132][gh-1132])
|
970 |
+
|
971 |
+
- Only return 201 on Create. Update should be 200
|
972 |
+
|
973 |
+
(props @danielbachhuber, [#1142][gh-1142])
|
974 |
+
|
975 |
+
- Convert meta endpoints to new-style
|
976 |
+
|
977 |
+
(props @rmccue, @rachelbaker, [#960][gh-960])
|
978 |
+
|
979 |
+
- Specific error codes for permissions failures
|
980 |
+
|
981 |
+
(props @joehoyle, [#1148][gh-1148])
|
982 |
+
|
983 |
+
[View all changes](https://github.com/WP-API/WP-API/compare/1.2.1...2.0-beta1)
|
984 |
+
[gh-347]: https://github.com/WP-API/WP-API/issues/347
|
985 |
+
[gh-378]: https://github.com/WP-API/WP-API/issues/378
|
986 |
+
[gh-401]: https://github.com/WP-API/WP-API/issues/401
|
987 |
+
[gh-415]: https://github.com/WP-API/WP-API/issues/415
|
988 |
+
[gh-448]: https://github.com/WP-API/WP-API/issues/448
|
989 |
+
[gh-474]: https://github.com/WP-API/WP-API/issues/474
|
990 |
+
[gh-481]: https://github.com/WP-API/WP-API/issues/481
|
991 |
+
[gh-524]: https://github.com/WP-API/WP-API/issues/524
|
992 |
+
[gh-528]: https://github.com/WP-API/WP-API/issues/528
|
993 |
+
[gh-543]: https://github.com/WP-API/WP-API/issues/543
|
994 |
+
[gh-546]: https://github.com/WP-API/WP-API/issues/546
|
995 |
+
[gh-548]: https://github.com/WP-API/WP-API/issues/548
|
996 |
+
[gh-549]: https://github.com/WP-API/WP-API/issues/549
|
997 |
+
[gh-550]: https://github.com/WP-API/WP-API/issues/550
|
998 |
+
[gh-556]: https://github.com/WP-API/WP-API/issues/556
|
999 |
+
[gh-563]: https://github.com/WP-API/WP-API/issues/563
|
1000 |
+
[gh-564]: https://github.com/WP-API/WP-API/issues/564
|
1001 |
+
[gh-565]: https://github.com/WP-API/WP-API/issues/565
|
1002 |
+
[gh-566]: https://github.com/WP-API/WP-API/issues/566
|
1003 |
+
[gh-567]: https://github.com/WP-API/WP-API/issues/567
|
1004 |
+
[gh-570]: https://github.com/WP-API/WP-API/issues/570
|
1005 |
+
[gh-573]: https://github.com/WP-API/WP-API/issues/573
|
1006 |
+
[gh-575]: https://github.com/WP-API/WP-API/issues/575
|
1007 |
+
[gh-579]: https://github.com/WP-API/WP-API/issues/579
|
1008 |
+
[gh-586]: https://github.com/WP-API/WP-API/issues/586
|
1009 |
+
[gh-588]: https://github.com/WP-API/WP-API/issues/588
|
1010 |
+
[gh-589]: https://github.com/WP-API/WP-API/issues/589
|
1011 |
+
[gh-591]: https://github.com/WP-API/WP-API/issues/591
|
1012 |
+
[gh-595]: https://github.com/WP-API/WP-API/issues/595
|
1013 |
+
[gh-602]: https://github.com/WP-API/WP-API/issues/602
|
1014 |
+
[gh-603]: https://github.com/WP-API/WP-API/issues/603
|
1015 |
+
[gh-612]: https://github.com/WP-API/WP-API/issues/612
|
1016 |
+
[gh-619]: https://github.com/WP-API/WP-API/issues/619
|
1017 |
+
[gh-620]: https://github.com/WP-API/WP-API/issues/620
|
1018 |
+
[gh-626]: https://github.com/WP-API/WP-API/issues/626
|
1019 |
+
[gh-628]: https://github.com/WP-API/WP-API/issues/628
|
1020 |
+
[gh-630]: https://github.com/WP-API/WP-API/issues/630
|
1021 |
+
[gh-637]: https://github.com/WP-API/WP-API/issues/637
|
1022 |
+
[gh-638]: https://github.com/WP-API/WP-API/issues/638
|
1023 |
+
[gh-643]: https://github.com/WP-API/WP-API/issues/643
|
1024 |
+
[gh-644]: https://github.com/WP-API/WP-API/issues/644
|
1025 |
+
[gh-645]: https://github.com/WP-API/WP-API/issues/645
|
1026 |
+
[gh-647]: https://github.com/WP-API/WP-API/issues/647
|
1027 |
+
[gh-648]: https://github.com/WP-API/WP-API/issues/648
|
1028 |
+
[gh-649]: https://github.com/WP-API/WP-API/issues/649
|
1029 |
+
[gh-652]: https://github.com/WP-API/WP-API/issues/652
|
1030 |
+
[gh-654]: https://github.com/WP-API/WP-API/issues/654
|
1031 |
+
[gh-656]: https://github.com/WP-API/WP-API/issues/656
|
1032 |
+
[gh-659]: https://github.com/WP-API/WP-API/issues/659
|
1033 |
+
[gh-661]: https://github.com/WP-API/WP-API/issues/661
|
1034 |
+
[gh-664]: https://github.com/WP-API/WP-API/issues/664
|
1035 |
+
[gh-666]: https://github.com/WP-API/WP-API/issues/666
|
1036 |
+
[gh-673]: https://github.com/WP-API/WP-API/issues/673
|
1037 |
+
[gh-675]: https://github.com/WP-API/WP-API/issues/675
|
1038 |
+
[gh-676]: https://github.com/WP-API/WP-API/issues/676
|
1039 |
+
[gh-678]: https://github.com/WP-API/WP-API/issues/678
|
1040 |
+
[gh-681]: https://github.com/WP-API/WP-API/issues/681
|
1041 |
+
[gh-682]: https://github.com/WP-API/WP-API/issues/682
|
1042 |
+
[gh-683]: https://github.com/WP-API/WP-API/issues/683
|
1043 |
+
[gh-684]: https://github.com/WP-API/WP-API/issues/684
|
1044 |
+
[gh-685]: https://github.com/WP-API/WP-API/issues/685
|
1045 |
+
[gh-692]: https://github.com/WP-API/WP-API/issues/692
|
1046 |
+
[gh-693]: https://github.com/WP-API/WP-API/issues/693
|
1047 |
+
[gh-696]: https://github.com/WP-API/WP-API/issues/696
|
1048 |
+
[gh-700]: https://github.com/WP-API/WP-API/issues/700
|
1049 |
+
[gh-701]: https://github.com/WP-API/WP-API/issues/701
|
1050 |
+
[gh-705]: https://github.com/WP-API/WP-API/issues/705
|
1051 |
+
[gh-707]: https://github.com/WP-API/WP-API/issues/707
|
1052 |
+
[gh-712]: https://github.com/WP-API/WP-API/issues/712
|
1053 |
+
[gh-714]: https://github.com/WP-API/WP-API/issues/714
|
1054 |
+
[gh-715]: https://github.com/WP-API/WP-API/issues/715
|
1055 |
+
[gh-722]: https://github.com/WP-API/WP-API/issues/722
|
1056 |
+
[gh-728]: https://github.com/WP-API/WP-API/issues/728
|
1057 |
+
[gh-730]: https://github.com/WP-API/WP-API/issues/730
|
1058 |
+
[gh-731]: https://github.com/WP-API/WP-API/issues/731
|
1059 |
+
[gh-736]: https://github.com/WP-API/WP-API/issues/736
|
1060 |
+
[gh-737]: https://github.com/WP-API/WP-API/issues/737
|
1061 |
+
[gh-741]: https://github.com/WP-API/WP-API/issues/741
|
1062 |
+
[gh-742]: https://github.com/WP-API/WP-API/issues/742
|
1063 |
+
[gh-743]: https://github.com/WP-API/WP-API/issues/743
|
1064 |
+
[gh-744]: https://github.com/WP-API/WP-API/issues/744
|
1065 |
+
[gh-750]: https://github.com/WP-API/WP-API/issues/750
|
1066 |
+
[gh-753]: https://github.com/WP-API/WP-API/issues/753
|
1067 |
+
[gh-758]: https://github.com/WP-API/WP-API/issues/758
|
1068 |
+
[gh-761]: https://github.com/WP-API/WP-API/issues/761
|
1069 |
+
[gh-774]: https://github.com/WP-API/WP-API/issues/774
|
1070 |
+
[gh-786]: https://github.com/WP-API/WP-API/issues/786
|
1071 |
+
[gh-794]: https://github.com/WP-API/WP-API/issues/794
|
1072 |
+
[gh-805]: https://github.com/WP-API/WP-API/issues/805
|
1073 |
+
[gh-807]: https://github.com/WP-API/WP-API/issues/807
|
1074 |
+
[gh-815]: https://github.com/WP-API/WP-API/issues/815
|
1075 |
+
[gh-820]: https://github.com/WP-API/WP-API/issues/820
|
1076 |
+
[gh-823]: https://github.com/WP-API/WP-API/issues/823
|
1077 |
+
[gh-826]: https://github.com/WP-API/WP-API/issues/826
|
1078 |
+
[gh-831]: https://github.com/WP-API/WP-API/issues/831
|
1079 |
+
[gh-832]: https://github.com/WP-API/WP-API/issues/832
|
1080 |
+
[gh-836]: https://github.com/WP-API/WP-API/issues/836
|
1081 |
+
[gh-838]: https://github.com/WP-API/WP-API/issues/838
|
1082 |
+
[gh-841]: https://github.com/WP-API/WP-API/issues/841
|
1083 |
+
[gh-842]: https://github.com/WP-API/WP-API/issues/842
|
1084 |
+
[gh-844]: https://github.com/WP-API/WP-API/issues/844
|
1085 |
+
[gh-845]: https://github.com/WP-API/WP-API/issues/845
|
1086 |
+
[gh-849]: https://github.com/WP-API/WP-API/issues/849
|
1087 |
+
[gh-853]: https://github.com/WP-API/WP-API/issues/853
|
1088 |
+
[gh-854]: https://github.com/WP-API/WP-API/issues/854
|
1089 |
+
[gh-870]: https://github.com/WP-API/WP-API/issues/870
|
1090 |
+
[gh-872]: https://github.com/WP-API/WP-API/issues/872
|
1091 |
+
[gh-874]: https://github.com/WP-API/WP-API/issues/874
|
1092 |
+
[gh-876]: https://github.com/WP-API/WP-API/issues/876
|
1093 |
+
[gh-879]: https://github.com/WP-API/WP-API/issues/879
|
1094 |
+
[gh-883]: https://github.com/WP-API/WP-API/issues/883
|
1095 |
+
[gh-885]: https://github.com/WP-API/WP-API/issues/885
|
1096 |
+
[gh-888]: https://github.com/WP-API/WP-API/issues/888
|
1097 |
+
[gh-891]: https://github.com/WP-API/WP-API/issues/891
|
1098 |
+
[gh-896]: https://github.com/WP-API/WP-API/issues/896
|
1099 |
+
[gh-897]: https://github.com/WP-API/WP-API/issues/897
|
1100 |
+
[gh-902]: https://github.com/WP-API/WP-API/issues/902
|
1101 |
+
[gh-904]: https://github.com/WP-API/WP-API/issues/904
|
1102 |
+
[gh-905]: https://github.com/WP-API/WP-API/issues/905
|
1103 |
+
[gh-906]: https://github.com/WP-API/WP-API/issues/906
|
1104 |
+
[gh-907]: https://github.com/WP-API/WP-API/issues/907
|
1105 |
+
[gh-908]: https://github.com/WP-API/WP-API/issues/908
|
1106 |
+
[gh-909]: https://github.com/WP-API/WP-API/issues/909
|
1107 |
+
[gh-910]: https://github.com/WP-API/WP-API/issues/910
|
1108 |
+
[gh-911]: https://github.com/WP-API/WP-API/issues/911
|
1109 |
+
[gh-913]: https://github.com/WP-API/WP-API/issues/913
|
1110 |
+
[gh-914]: https://github.com/WP-API/WP-API/issues/914
|
1111 |
+
[gh-917]: https://github.com/WP-API/WP-API/issues/917
|
1112 |
+
[gh-919]: https://github.com/WP-API/WP-API/issues/919
|
1113 |
+
[gh-923]: https://github.com/WP-API/WP-API/issues/923
|
1114 |
+
[gh-931]: https://github.com/WP-API/WP-API/issues/931
|
1115 |
+
[gh-933]: https://github.com/WP-API/WP-API/issues/933
|
1116 |
+
[gh-935]: https://github.com/WP-API/WP-API/issues/935
|
1117 |
+
[gh-936]: https://github.com/WP-API/WP-API/issues/936
|
1118 |
+
[gh-937]: https://github.com/WP-API/WP-API/issues/937
|
1119 |
+
[gh-941]: https://github.com/WP-API/WP-API/issues/941
|
1120 |
+
[gh-946]: https://github.com/WP-API/WP-API/issues/946
|
1121 |
+
[gh-947]: https://github.com/WP-API/WP-API/issues/947
|
1122 |
+
[gh-951]: https://github.com/WP-API/WP-API/issues/951
|
1123 |
+
[gh-953]: https://github.com/WP-API/WP-API/issues/953
|
1124 |
+
[gh-955]: https://github.com/WP-API/WP-API/issues/955
|
1125 |
+
[gh-958]: https://github.com/WP-API/WP-API/issues/958
|
1126 |
+
[gh-959]: https://github.com/WP-API/WP-API/issues/959
|
1127 |
+
[gh-960]: https://github.com/WP-API/WP-API/issues/960
|
1128 |
+
[gh-966]: https://github.com/WP-API/WP-API/issues/966
|
1129 |
+
[gh-967]: https://github.com/WP-API/WP-API/issues/967
|
1130 |
+
[gh-968]: https://github.com/WP-API/WP-API/issues/968
|
1131 |
+
[gh-970]: https://github.com/WP-API/WP-API/issues/970
|
1132 |
+
[gh-971]: https://github.com/WP-API/WP-API/issues/971
|
1133 |
+
[gh-973]: https://github.com/WP-API/WP-API/issues/973
|
1134 |
+
[gh-985]: https://github.com/WP-API/WP-API/issues/985
|
1135 |
+
[gh-987]: https://github.com/WP-API/WP-API/issues/987
|
1136 |
+
[gh-989]: https://github.com/WP-API/WP-API/issues/989
|
1137 |
+
[gh-996]: https://github.com/WP-API/WP-API/issues/996
|
1138 |
+
[gh-999]: https://github.com/WP-API/WP-API/issues/999
|
1139 |
+
[gh-1000]: https://github.com/WP-API/WP-API/issues/1000
|
1140 |
+
[gh-1006]: https://github.com/WP-API/WP-API/issues/1006
|
1141 |
+
[gh-1026]: https://github.com/WP-API/WP-API/issues/1026
|
1142 |
+
[gh-1028]: https://github.com/WP-API/WP-API/issues/1028
|
1143 |
+
[gh-1033]: https://github.com/WP-API/WP-API/issues/1033
|
1144 |
+
[gh-1034]: https://github.com/WP-API/WP-API/issues/1034
|
1145 |
+
[gh-1039]: https://github.com/WP-API/WP-API/issues/1039
|
1146 |
+
[gh-1042]: https://github.com/WP-API/WP-API/issues/1042
|
1147 |
+
[gh-1043]: https://github.com/WP-API/WP-API/issues/1043
|
1148 |
+
[gh-1044]: https://github.com/WP-API/WP-API/issues/1044
|
1149 |
+
[gh-1046]: https://github.com/WP-API/WP-API/issues/1046
|
1150 |
+
[gh-1048]: https://github.com/WP-API/WP-API/issues/1048
|
1151 |
+
[gh-1049]: https://github.com/WP-API/WP-API/issues/1049
|
1152 |
+
[gh-1050]: https://github.com/WP-API/WP-API/issues/1050
|
1153 |
+
[gh-1051]: https://github.com/WP-API/WP-API/issues/1051
|
1154 |
+
[gh-1052]: https://github.com/WP-API/WP-API/issues/1052
|
1155 |
+
[gh-1053]: https://github.com/WP-API/WP-API/issues/1053
|
1156 |
+
[gh-1054]: https://github.com/WP-API/WP-API/issues/1054
|
1157 |
+
[gh-1059]: https://github.com/WP-API/WP-API/issues/1059
|
1158 |
+
[gh-1064]: https://github.com/WP-API/WP-API/issues/1064
|
1159 |
+
[gh-1066]: https://github.com/WP-API/WP-API/issues/1066
|
1160 |
+
[gh-1091]: https://github.com/WP-API/WP-API/issues/1091
|
1161 |
+
[gh-1097]: https://github.com/WP-API/WP-API/issues/1097
|
1162 |
+
[gh-1098]: https://github.com/WP-API/WP-API/issues/1098
|
1163 |
+
[gh-1103]: https://github.com/WP-API/WP-API/issues/1103
|
1164 |
+
[gh-1104]: https://github.com/WP-API/WP-API/issues/1104
|
1165 |
+
[gh-1105]: https://github.com/WP-API/WP-API/issues/1105
|
1166 |
+
[gh-1106]: https://github.com/WP-API/WP-API/issues/1106
|
1167 |
+
[gh-1108]: https://github.com/WP-API/WP-API/issues/1108
|
1168 |
+
[gh-1109]: https://github.com/WP-API/WP-API/issues/1109
|
1169 |
+
[gh-1110]: https://github.com/WP-API/WP-API/issues/1110
|
1170 |
+
[gh-1111]: https://github.com/WP-API/WP-API/issues/1111
|
1171 |
+
[gh-1112]: https://github.com/WP-API/WP-API/issues/1112
|
1172 |
+
[gh-1115]: https://github.com/WP-API/WP-API/issues/1115
|
1173 |
+
[gh-1116]: https://github.com/WP-API/WP-API/issues/1116
|
1174 |
+
[gh-1117]: https://github.com/WP-API/WP-API/issues/1117
|
1175 |
+
[gh-1121]: https://github.com/WP-API/WP-API/issues/1121
|
1176 |
+
[gh-1122]: https://github.com/WP-API/WP-API/issues/1122
|
1177 |
+
[gh-1123]: https://github.com/WP-API/WP-API/issues/1123
|
1178 |
+
[gh-1129]: https://github.com/WP-API/WP-API/issues/1129
|
1179 |
+
[gh-1131]: https://github.com/WP-API/WP-API/issues/1131
|
1180 |
+
[gh-1132]: https://github.com/WP-API/WP-API/issues/1132
|
1181 |
+
[gh-1133]: https://github.com/WP-API/WP-API/issues/1133
|
1182 |
+
[gh-1134]: https://github.com/WP-API/WP-API/issues/1134
|
1183 |
+
[gh-1137]: https://github.com/WP-API/WP-API/issues/1137
|
1184 |
+
[gh-1142]: https://github.com/WP-API/WP-API/issues/1142
|
1185 |
+
[gh-1148]: https://github.com/WP-API/WP-API/issues/1148
|
1186 |
+
|
1187 |
+
## 1.2.1
|
1188 |
+
|
1189 |
+
- Fix information disclosure security vulnerability.
|
1190 |
+
|
1191 |
+
Unauthenticated users could access revisions of published and unpublished posts. Revisions are now only accessible to authenticated users with permission to edit the revision's post.
|
1192 |
+
|
1193 |
+
Reported by @chredd on 2015-04-09.
|
1194 |
+
|
1195 |
+
## 1.2.0
|
1196 |
+
|
1197 |
+
- Add handling for Cross-Origin Resource Sharing (CORS) OPTIONS requests.
|
1198 |
+
|
1199 |
+
Preflighted requests (using the OPTIONS method) include the headers
|
1200 |
+
`Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, and
|
1201 |
+
`Access-Control-Allow-Credentials` in the response, if the HTTP origin is
|
1202 |
+
set.
|
1203 |
+
|
1204 |
+
(props @rmccue, [#281][gh-281])
|
1205 |
+
|
1206 |
+
- Allow overriding full requests.
|
1207 |
+
|
1208 |
+
The `json_pre_dispatch` filter allows a request to be hijacked before it is
|
1209 |
+
dispatched. Hijacked requests can be anything a normal endpoint can return.
|
1210 |
+
|
1211 |
+
(props @rmccue, [#281][gh-281])
|
1212 |
+
|
1213 |
+
- Check for JSON encoding/decoding errors.
|
1214 |
+
|
1215 |
+
Returns the last error (if any) occurred during the last JSON encoding or
|
1216 |
+
decoding operation.
|
1217 |
+
|
1218 |
+
(props @joshkadis, @rmccue, [#461][gh-461])
|
1219 |
+
|
1220 |
+
- Add filtering to the terms collection endpoint.
|
1221 |
+
|
1222 |
+
Available filter arguments are based on the `get_terms()` function. Example:
|
1223 |
+
`/taxonomies/category/terms?filter[number]=10` would limit the response to 10
|
1224 |
+
category terms.
|
1225 |
+
|
1226 |
+
(props @mauteri, [#401][gh-401], [#347][gh-347])
|
1227 |
+
|
1228 |
+
- Add handling for the `role` parameter when creating or updating a user.
|
1229 |
+
|
1230 |
+
Allow users to be created or updated with a provided `role`.
|
1231 |
+
|
1232 |
+
(props @pippinsplugins, [#392][gh-392], [#335][gh-335])
|
1233 |
+
|
1234 |
+
- Add handling for the `post_id` parameter when creating media.
|
1235 |
+
|
1236 |
+
Allow passing the `post_id` parameter to associate a new media item with
|
1237 |
+
a post.
|
1238 |
+
|
1239 |
+
(props @pkevan, [#294][gh-294])
|
1240 |
+
|
1241 |
+
- Handle route matching for `-` in taxonomy and terms.
|
1242 |
+
|
1243 |
+
Previously the regular expression used to match taxonomy and term names did
|
1244 |
+
not support names with dashes.
|
1245 |
+
|
1246 |
+
(props @EdHurtig, @evansobkowicz, [#410][gh-410])
|
1247 |
+
|
1248 |
+
- Handle JSONP callback matching for `.` in the function name.
|
1249 |
+
|
1250 |
+
Previously the regular expression used to match JSONP callback functions did
|
1251 |
+
not support names with periods.
|
1252 |
+
|
1253 |
+
(props @codonnell822, [#455][gh-455])
|
1254 |
+
|
1255 |
+
- Fix the Content-Type header for JSONP requests.
|
1256 |
+
|
1257 |
+
Previously JSONP requests sent the incorrect `application/json` Content-Type
|
1258 |
+
header with the response. This would result in an error if strict MIME
|
1259 |
+
checking was enabled. The Content-Type header was corrected to
|
1260 |
+
`application/javascript` for JSONP responses.
|
1261 |
+
|
1262 |
+
(props @simonlampen, [#380][gh-380])
|
1263 |
+
|
1264 |
+
- Add `$context` parameter to `json_prepare_term` filter.
|
1265 |
+
|
1266 |
+
Terms responses can now be modified based on the `context` parameter of the
|
1267 |
+
request.
|
1268 |
+
|
1269 |
+
(props @traversal, [#316][gh-316])
|
1270 |
+
|
1271 |
+
- Move the JavaScript client library into the plugin.
|
1272 |
+
|
1273 |
+
Previously, the `wp-api.js` file was a separate repository. The JavaScript
|
1274 |
+
client has moved back into the plugin to coordinate code changes.
|
1275 |
+
|
1276 |
+
(props @tlovett1, [#730][gh-730])
|
1277 |
+
|
1278 |
+
- Always return an object for media sizes
|
1279 |
+
|
1280 |
+
The media sizes value should always be an object even when empty. Previously,
|
1281 |
+
if a media item did not have any sizes set, an empty array was returned.
|
1282 |
+
|
1283 |
+
**Compatibility warning**: Clients should be prepared to accept an empty
|
1284 |
+
object as a value for media sizes.
|
1285 |
+
|
1286 |
+
(props @maxcutler, [#300][gh-300])
|
1287 |
+
|
1288 |
+
- Give top-level posts a `null` parent value.
|
1289 |
+
|
1290 |
+
For date type consistency, post parent property should be `null`. Previously,
|
1291 |
+
parent-less posts returned `0` for parent.
|
1292 |
+
|
1293 |
+
**Compatibility warning**: Clients should be prepared to accept `null` as a
|
1294 |
+
value for post parent.
|
1295 |
+
|
1296 |
+
(props @maxcutler, [#391][gh-391])
|
1297 |
+
|
1298 |
+
- Move permission checks out of `WP_JSON_Posts`.
|
1299 |
+
|
1300 |
+
Introduce `json_check_post_permission()` function to allow post object
|
1301 |
+
capability checks to be used outside the `WP_JSON_Posts` class.
|
1302 |
+
|
1303 |
+
**Deprecation warning:** Calling `WP_JSON_Posts::check_read_permission` and
|
1304 |
+
`WP_JSON_Posts::check_edit_permission` is now deprecated.
|
1305 |
+
|
1306 |
+
(props @rachelbaker, [#486][gh-486], [#378][gh-378])
|
1307 |
+
|
1308 |
+
- Split comment endpoints into separate class.
|
1309 |
+
|
1310 |
+
All comment handling has moved to the `WP_JSON_Comments` class.
|
1311 |
+
|
1312 |
+
**Deprecation warning:** Calling `WP_JSON_Posts::get_comments`,
|
1313 |
+
`WP_JSON_Posts::get_comment`, `WP_JSON_Posts::delete_comment`, and
|
1314 |
+
`WP_JSON_Posts::prepare_comment` is now deprecated.
|
1315 |
+
|
1316 |
+
(props @whyisjake, @rmccue, @rachelbaker, [#378][gh-378])
|
1317 |
+
|
1318 |
+
- Split meta endpoints into separate class.
|
1319 |
+
|
1320 |
+
All post meta handling has moved to the new `WP_JSON_Meta_Posts` class.
|
1321 |
+
|
1322 |
+
**Deprecation warning:** Calling `WP_JSON_Posts::get_all_meta`,
|
1323 |
+
`WP_JSON_Posts::get_meta`, `WP_JSON_Posts::update_meta`,
|
1324 |
+
`WP_JSON_Posts::add_meta`, `WP_JSON_Posts::delete_meta`,
|
1325 |
+
`WP_JSON_Posts::prepare_meta`, and `WP_JSON_Posts::is_valid_meta_data` is
|
1326 |
+
now deprecated.
|
1327 |
+
|
1328 |
+
(props @rmccue, @rachelbaker, [#358][gh-358], [#474][gh-474])
|
1329 |
+
|
1330 |
+
- Rename internal create methods.
|
1331 |
+
|
1332 |
+
**Deprecation warning:** Calling `WP_JSON_Posts::new_post`,
|
1333 |
+
`WP_JSON_CustomPostType::new_post` and `WP_JSON_Posts::new_post`
|
1334 |
+
is now deprecated.
|
1335 |
+
|
1336 |
+
(props @rachelbaker, @rmccue, [#374][gh-374], [#377][gh-377], [#376][gh-376])
|
1337 |
+
|
1338 |
+
- Fix discrepancies in edit and create posts documentation examples.
|
1339 |
+
|
1340 |
+
Corrected the edit and create posts code examples in the Getting Started
|
1341 |
+
section. The new post example was updated to include the required
|
1342 |
+
`content_raw` parameter. The new and edit posts examples were updated to use
|
1343 |
+
a correct date parameter.
|
1344 |
+
|
1345 |
+
(props @rachelbaker, [#305][gh-305])
|
1346 |
+
|
1347 |
+
- Update the cookie authentication documentation examples.
|
1348 |
+
|
1349 |
+
With 1.1 the localized JavaScript object for `wp-api.js` changed to
|
1350 |
+
`WP_API_Settings`. This updates the Authentication section documentation
|
1351 |
+
nonce example to use the updated object name.
|
1352 |
+
|
1353 |
+
(props @rachelbaker, [#321][gh-321])
|
1354 |
+
|
1355 |
+
- Add flexibility and multisite support to unit tests.
|
1356 |
+
|
1357 |
+
Tests can be run from any WordPress install, and are not limited to only as
|
1358 |
+
a plugin installed within a WordPress.org develop checkout. Unit tests are
|
1359 |
+
now run against a multisite installation.
|
1360 |
+
|
1361 |
+
(props @danielbachhuber, [#397][gh-397])
|
1362 |
+
|
1363 |
+
- Add `taxonomy` slug to the term response.
|
1364 |
+
|
1365 |
+
(props @kalenjohnson, [#481][gh-481])
|
1366 |
+
|
1367 |
+
- Fix error when getting child comment.
|
1368 |
+
|
1369 |
+
Previously an error occurred when a requested comment had a parent.
|
1370 |
+
|
1371 |
+
(props @EdHurtig, [#413][gh-413], [#411][gh-411])
|
1372 |
+
|
1373 |
+
- Parse query strings before returning a JSON decode error.
|
1374 |
+
|
1375 |
+
(props @jtsternberg, [#499][gh-499])
|
1376 |
+
|
1377 |
+
- Typecast the user ID parameter to be an integer for the `/users/{ID}` route.
|
1378 |
+
|
1379 |
+
(props @dimadin, [#333][gh-333])
|
1380 |
+
|
1381 |
+
- Confirm a given JSONP callback is a string.
|
1382 |
+
|
1383 |
+
(props @ircrash, @rmccue, [#405][gh-405])
|
1384 |
+
|
1385 |
+
- Register the JavaScript client in the admin.
|
1386 |
+
|
1387 |
+
(props @tlovett1, [#473][gh-473])
|
1388 |
+
|
1389 |
+
- Remove duplicate error checks on post ids.
|
1390 |
+
|
1391 |
+
(props @danielbachhuber, [#271][gh-271])
|
1392 |
+
|
1393 |
+
- Update documentation link references to wp-api.org.
|
1394 |
+
|
1395 |
+
(props @pollyplummer, [#320][gh-320])
|
1396 |
+
|
1397 |
+
- Update documentation to note routes needing authentication.
|
1398 |
+
|
1399 |
+
(props @kellbot, [#402][gh-402], [#309][gh-309])
|
1400 |
+
|
1401 |
+
- Correct Post route documentation filter parameters.
|
1402 |
+
|
1403 |
+
(props @modemlooper, @rachelbaker, @rmccue, [#357][gh-357], [#462][gh-462])
|
1404 |
+
|
1405 |
+
- Update taxonomy route documentation with correct paths.
|
1406 |
+
|
1407 |
+
(props @davidbhayes, [#364][gh-364], [#355][gh-355])
|
1408 |
+
|
1409 |
+
- Remove references to legacy `$fields` parameter.
|
1410 |
+
|
1411 |
+
(props @JDGrimes, [#326][gh-326])
|
1412 |
+
|
1413 |
+
- Alter readme installation steps to use wp-cli for plugin and permalink setup.
|
1414 |
+
|
1415 |
+
(props @kadamwhite, [#390][gh-390])
|
1416 |
+
|
1417 |
+
- Add steps to readme for executing tests with `vagrant ssh -c`.
|
1418 |
+
|
1419 |
+
(props @kadamwhite, [#416][gh-416])
|
1420 |
+
|
1421 |
+
- Update readme to include provision step for testing suite.
|
1422 |
+
|
1423 |
+
(props @ironpaperweight, [#396][gh-396])
|
1424 |
+
|
1425 |
+
- Update readme Getting Started link.
|
1426 |
+
|
1427 |
+
(props @NikV, [#519][gh-519])
|
1428 |
+
|
1429 |
+
- Update readme Chassis repository links.
|
1430 |
+
|
1431 |
+
(props @Japh, [#505][gh-505])
|
1432 |
+
|
1433 |
+
- Clean-up of `docs` folder.
|
1434 |
+
|
1435 |
+
(props @pollyplummer, [#441][gh-441])
|
1436 |
+
|
1437 |
+
- Documentation audit for plugin.php file.
|
1438 |
+
|
1439 |
+
(props @DrewAPicture, [#293][gh-293])
|
1440 |
+
|
1441 |
+
- Rename tests to match class file naming.
|
1442 |
+
|
1443 |
+
(props @danielbachhuber, @rmccue, [#359][gh-359])
|
1444 |
+
|
1445 |
+
- Add license.txt file with license terms.
|
1446 |
+
|
1447 |
+
(props @rachelbaker, [#393][gh-393], [#384][gh-384])
|
1448 |
+
|
1449 |
+
- Fix test_root when using WordPress.org developer checkout.
|
1450 |
+
|
1451 |
+
(props @markoheijnen, [#437][gh-437])
|
1452 |
+
|
1453 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/1.1.1...1.2)
|
1454 |
+
|
1455 |
+
[gh-271]: https://github.com/WP-API/WP-API/issues/271
|
1456 |
+
[gh-281]: https://github.com/WP-API/WP-API/issues/281
|
1457 |
+
[gh-293]: https://github.com/WP-API/WP-API/issues/293
|
1458 |
+
[gh-294]: https://github.com/WP-API/WP-API/issues/294
|
1459 |
+
[gh-300]: https://github.com/WP-API/WP-API/issues/300
|
1460 |
+
[gh-305]: https://github.com/WP-API/WP-API/issues/305
|
1461 |
+
[gh-309]: https://github.com/WP-API/WP-API/issues/309
|
1462 |
+
[gh-316]: https://github.com/WP-API/WP-API/issues/316
|
1463 |
+
[gh-320]: https://github.com/WP-API/WP-API/issues/320
|
1464 |
+
[gh-321]: https://github.com/WP-API/WP-API/issues/321
|
1465 |
+
[gh-326]: https://github.com/WP-API/WP-API/issues/326
|
1466 |
+
[gh-333]: https://github.com/WP-API/WP-API/issues/333
|
1467 |
+
[gh-333]: https://github.com/WP-API/WP-API/issues/333
|
1468 |
+
[gh-335]: https://github.com/WP-API/WP-API/issues/335
|
1469 |
+
[gh-347]: https://github.com/WP-API/WP-API/issues/347
|
1470 |
+
[gh-355]: https://github.com/WP-API/WP-API/issues/355
|
1471 |
+
[gh-357]: https://github.com/WP-API/WP-API/issues/357
|
1472 |
+
[gh-358]: https://github.com/WP-API/WP-API/issues/358
|
1473 |
+
[gh-359]: https://github.com/WP-API/WP-API/issues/359
|
1474 |
+
[gh-364]: https://github.com/WP-API/WP-API/issues/364
|
1475 |
+
[gh-374]: https://github.com/WP-API/WP-API/issues/374
|
1476 |
+
[gh-376]: https://github.com/WP-API/WP-API/issues/376
|
1477 |
+
[gh-377]: https://github.com/WP-API/WP-API/issues/377
|
1478 |
+
[gh-378]: https://github.com/WP-API/WP-API/issues/378
|
1479 |
+
[gh-380]: https://github.com/WP-API/WP-API/issues/380
|
1480 |
+
[gh-384]: https://github.com/WP-API/WP-API/issues/384
|
1481 |
+
[gh-390]: https://github.com/WP-API/WP-API/issues/390
|
1482 |
+
[gh-391]: https://github.com/WP-API/WP-API/issues/391
|
1483 |
+
[gh-392]: https://github.com/WP-API/WP-API/issues/392
|
1484 |
+
[gh-393]: https://github.com/WP-API/WP-API/issues/393
|
1485 |
+
[gh-396]: https://github.com/WP-API/WP-API/issues/396
|
1486 |
+
[gh-397]: https://github.com/WP-API/WP-API/issues/397
|
1487 |
+
[gh-401]: https://github.com/WP-API/WP-API/issues/401
|
1488 |
+
[gh-402]: https://github.com/WP-API/WP-API/issues/402
|
1489 |
+
[gh-405]: https://github.com/WP-API/WP-API/issues/405
|
1490 |
+
[gh-410]: https://github.com/WP-API/WP-API/issues/410
|
1491 |
+
[gh-411]: https://github.com/WP-API/WP-API/issues/411
|
1492 |
+
[gh-413]: https://github.com/WP-API/WP-API/issues/413
|
1493 |
+
[gh-416]: https://github.com/WP-API/WP-API/issues/416
|
1494 |
+
[gh-437]: https://github.com/WP-API/WP-API/issues/437
|
1495 |
+
[gh-438]: https://github.com/WP-API/WP-API/issues/438
|
1496 |
+
[gh-441]: https://github.com/WP-API/WP-API/issues/441
|
1497 |
+
[gh-455]: https://github.com/WP-API/WP-API/issues/455
|
1498 |
+
[gh-458]: https://github.com/WP-API/WP-API/issues/458
|
1499 |
+
[gh-461]: https://github.com/WP-API/WP-API/issues/461
|
1500 |
+
[gh-462]: https://github.com/WP-API/WP-API/issues/462
|
1501 |
+
[gh-473]: https://github.com/WP-API/WP-API/issues/473
|
1502 |
+
[gh-474]: https://github.com/WP-API/WP-API/issues/474
|
1503 |
+
[gh-481]: https://github.com/WP-API/WP-API/issues/481
|
1504 |
+
[gh-486]: https://github.com/WP-API/WP-API/issues/486
|
1505 |
+
[gh-499]: https://github.com/WP-API/WP-API/issues/499
|
1506 |
+
[gh-505]: https://github.com/WP-API/WP-API/issues/505
|
1507 |
+
[gh-519]: https://github.com/WP-API/WP-API/issues/519
|
1508 |
+
[gh-524]: https://github.com/WP-API/WP-API/issues/524
|
1509 |
+
[gh-528]: https://github.com/WP-API/WP-API/issues/528
|
1510 |
+
[gh-595]: https://github.com/WP-API/WP-API/issues/595
|
1511 |
+
[gh-730]: https://github.com/WP-API/WP-API/issues/730
|
1512 |
+
[gh-933]: https://github.com/WP-API/WP-API/issues/933
|
1513 |
+
[gh-985]: https://github.com/WP-API/WP-API/issues/985
|
1514 |
+
|
1515 |
+
## 1.1.1
|
1516 |
+
|
1517 |
+
- Mitigate Flash CSRF exploit
|
1518 |
+
|
1519 |
+
Using the API's JSONP support, it's possible to control the first bytes of the
|
1520 |
+
response sent to the browser. Combining this with an ASCII-encoded SWF allows
|
1521 |
+
arbitrary SWFs to be served from the site, allowing bypassing the same-origin
|
1522 |
+
policy built in to browsers.
|
1523 |
+
|
1524 |
+
While the API includes CSRF protection and is not directly vulnerable, this
|
1525 |
+
can be used to bypass other browser origin controls.
|
1526 |
+
|
1527 |
+
Reported by @iandunn on 2014-07-10.
|
1528 |
+
|
1529 |
+
(props @iandunn, @rmccue, [#356][gh-356])
|
1530 |
+
|
1531 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/1.0...1.1)
|
1532 |
+
|
1533 |
+
[gh-356]: https://github.com/WP-API/WP-API/issues/356
|
1534 |
+
|
1535 |
+
## 1.1
|
1536 |
+
|
1537 |
+
- Add new routes for taxonomies and terms.
|
1538 |
+
|
1539 |
+
Taxonomies and terms have now been moved from the `/posts/types/<type>`
|
1540 |
+
namespace to global routes: `/taxonomies`, `/taxonomies/<tax>`,
|
1541 |
+
`/taxonomies/<tax>/terms` and `/taxonomies/<tax>/terms/<term>`
|
1542 |
+
|
1543 |
+
Test coverage for taxonomy endpoints has also been increased to 100%.
|
1544 |
+
|
1545 |
+
**Deprecation warning**: The `/posts/types/<type>/taxonomies` endpoint (and
|
1546 |
+
sub-endpoints with the same prefix) have been deprecated in favour of the new
|
1547 |
+
endpoints. These deprecated endpoints will now return a
|
1548 |
+
`X-WP-DeprecatedFunction` header indicating that the endpoint should not be
|
1549 |
+
used for new development, but will continue to work in the future.
|
1550 |
+
|
1551 |
+
(props @kadamwhite, @rachelbaker, @rmccue, [#198][gh-198], [#211][gh-211])
|
1552 |
+
|
1553 |
+
- Allow customizing the API resources prefix
|
1554 |
+
|
1555 |
+
The API base (typically `wp-json/`) can now be customized to a different
|
1556 |
+
prefix using the `json_url_prefix` filter. Note that rewrites will need to be
|
1557 |
+
flushed manually after changing this.
|
1558 |
+
|
1559 |
+
(props @ericandrewlewis, @rmccue, [#104][gh-104], [#244][gh-244], [#278][gh-278])
|
1560 |
+
|
1561 |
+
- Give `null` as date for draft posts.
|
1562 |
+
|
1563 |
+
Draft posts would previously return "0000-00-00 00:00:00" or
|
1564 |
+
"1970-01-01T00:00:00", as draft posts are not assigned a publish date. The API
|
1565 |
+
now returns `null` where a date is not available.
|
1566 |
+
|
1567 |
+
**Compatibility warning**: Clients should be prepared to accept `null` as a
|
1568 |
+
value for date/time fields, and treat it as if no value is set.
|
1569 |
+
|
1570 |
+
(props @rmccue, [#229][gh-229], [#230][gh-230])
|
1571 |
+
|
1572 |
+
- Fix errors with excerpt.
|
1573 |
+
|
1574 |
+
Posts without excerpts could previously return nonsense strings, excerpts from
|
1575 |
+
other posts, or cause internal PHP errors. Posts without excerpts will now
|
1576 |
+
always return an excerpt, typically automatically generated from the post
|
1577 |
+
content.
|
1578 |
+
|
1579 |
+
The `excerpt_raw` field was added to the edit context on posts. This field
|
1580 |
+
contains the raw excerpt data saved for the post, including empty
|
1581 |
+
string values.
|
1582 |
+
|
1583 |
+
(props @rmccue, [#222][gh-226], [#226][gh-226])
|
1584 |
+
|
1585 |
+
- Only expose email for edit context.
|
1586 |
+
|
1587 |
+
User email addresses are now only exposed for `context=edit`, which requires
|
1588 |
+
the `edit_users` permission (not required for the current user).
|
1589 |
+
|
1590 |
+
The email address field will now return `false` instead of a string if the
|
1591 |
+
field is not exposed.
|
1592 |
+
|
1593 |
+
(props @pkevan, @rmccue, [#290][gh-290], [#296][gh-296])
|
1594 |
+
|
1595 |
+
- Correct password-protected post handling.
|
1596 |
+
|
1597 |
+
Password-protected posts could previously be exposed to all users, however
|
1598 |
+
could also have broken behaviour with excerpts. Password-protected posts are
|
1599 |
+
now hidden to unauthenticated users, while content and excerpts are shown
|
1600 |
+
correctly for the `edit` context.
|
1601 |
+
|
1602 |
+
(Note that hiding password-protected posts is intended to be a temporary
|
1603 |
+
measure, and will likely change in the future.)
|
1604 |
+
|
1605 |
+
(props @rmccue, [#286][gh-286], [#313][gh-313])
|
1606 |
+
|
1607 |
+
- Add documentation on authentication methods.
|
1608 |
+
|
1609 |
+
Full documentation on [authentication](https://github.com/WP-API/WP-API/blob/master/docs/authentication.md)
|
1610 |
+
is now available. This documentation explains the difference between the
|
1611 |
+
various available authentication methods, and notes which should be used.
|
1612 |
+
|
1613 |
+
(props @rmccue, [#242][gh-242])
|
1614 |
+
|
1615 |
+
- Include new client JS from github.io
|
1616 |
+
|
1617 |
+
The WP-API Javascript library is now loaded dynamically from
|
1618 |
+
`wp-api.github.io` to ensure it is always up-to-date.
|
1619 |
+
|
1620 |
+
(props @tlovett1, [#179][gh-179], [#240][gh-240])
|
1621 |
+
|
1622 |
+
- Don't allow setting the modification date on post creation/update.
|
1623 |
+
|
1624 |
+
As it turns out, WP core doesn't allow us to set this, so this was previously
|
1625 |
+
a no-op anyway. Discovered during test coverage phase.
|
1626 |
+
|
1627 |
+
(props @rachelbaker, @rmccue, [#285][gh-285], [#288][gh-288])
|
1628 |
+
|
1629 |
+
- Check post parent correctly on insertion.
|
1630 |
+
|
1631 |
+
Posts could previously be added with an invalid parent ID. These IDs are now
|
1632 |
+
checked to ensure the post exists.
|
1633 |
+
|
1634 |
+
(props @rmccue, [#228][gh-228], [#231][gh-231])
|
1635 |
+
|
1636 |
+
- Make sure the type is actually evaluated for `json_prepare_${type}` filter.
|
1637 |
+
|
1638 |
+
This value was previously not interpolated correctly, due to the use of the
|
1639 |
+
single-quoted string type.
|
1640 |
+
|
1641 |
+
(props @danielbachhuber, [#266][gh-266])
|
1642 |
+
|
1643 |
+
- Return `WP_Error` instead of array of empty objects for a revisions
|
1644 |
+
permissions error.
|
1645 |
+
|
1646 |
+
Previously, when trying to access post revisions without correct permissions,
|
1647 |
+
a JSON list of internal error objects would be returned. This has been
|
1648 |
+
corrected to return a standard API error instead.
|
1649 |
+
|
1650 |
+
(props @rachelbaker, @tlovett1, [#251][gh-251], [#276][gh-276])
|
1651 |
+
|
1652 |
+
- Flip user parameters check for insert/update.
|
1653 |
+
|
1654 |
+
Previously, you could add a user without specifying username/password/email,
|
1655 |
+
but couldn't update a user without those parameters. The logic has been
|
1656 |
+
inverted here instead.
|
1657 |
+
|
1658 |
+
(props @rmccue, [#221][gh-221], [#289][gh-289])
|
1659 |
+
|
1660 |
+
- Add revision endpoints tests
|
1661 |
+
|
1662 |
+
(props @danielbachhuber, @rachelbaker, @rmccue, [#275][gh-275], [#277][gh-277], [#284][gh-284], [#279][gh-279])
|
1663 |
+
|
1664 |
+
- Add post endpoint testing
|
1665 |
+
|
1666 |
+
Now at >54% coverage for the whole class, and >80% for the main methods. This
|
1667 |
+
figure will continue to rise over the next few releases.
|
1668 |
+
|
1669 |
+
(props @rachelbaker, @rmccue, [#99][gh-99])
|
1670 |
+
|
1671 |
+
- Separate helper functions into global namespace.
|
1672 |
+
|
1673 |
+
`WP_JSON_Server::get_timezone()`, `WP_JSON_Server::get_date_with_gmt()`,
|
1674 |
+
`WP_JSON_Server::get_avatar_url()` and ``WP_JSON_Server::parse_date()` have
|
1675 |
+
all been moved into the global namespace to decouple them from the server
|
1676 |
+
class.
|
1677 |
+
|
1678 |
+
**Deprecation warning**: These methods have been deprecated. The new
|
1679 |
+
`json_get_timezone()`, `json_get_date_with_gmt()`, `json_get_avatar_url()` and
|
1680 |
+
`json_parse_date()` methods should now be used instead.
|
1681 |
+
|
1682 |
+
(props @rmccue, [#185][gh-185], [#298][gh-298])
|
1683 |
+
|
1684 |
+
- Re-order Users and Media routes documentation based on CRUD order
|
1685 |
+
|
1686 |
+
(props @rachelbaker, [#214][gh-214])
|
1687 |
+
|
1688 |
+
- Update Post route documentation to provide more detail for data parameter
|
1689 |
+
|
1690 |
+
(props @rachelbaker, [#212][gh-212])
|
1691 |
+
|
1692 |
+
- Correct documentation typo ("inforcement" -> "enforcement").
|
1693 |
+
|
1694 |
+
(props @ericandrewlewis, [#236][gh-236])
|
1695 |
+
|
1696 |
+
- Coding Standards audit
|
1697 |
+
|
1698 |
+
(props @DrewAPicture, [#235][gh-235])
|
1699 |
+
|
1700 |
+
- Add comparison documentation.
|
1701 |
+
|
1702 |
+
(props @rachelbaker, @rmccue, [#217][gh-225], [#225][gh-225])
|
1703 |
+
|
1704 |
+
- `json_url` filter call should be passed `$scheme`
|
1705 |
+
|
1706 |
+
(props @ericandrewlewis, [#243][gh-243])
|
1707 |
+
|
1708 |
+
- Set `class-jsonserializable.php` file mode to 644.
|
1709 |
+
|
1710 |
+
(props @jeremyfelt, [#255][gh-255])
|
1711 |
+
|
1712 |
+
- Remove unneeded "which" in implementation doc.
|
1713 |
+
|
1714 |
+
(props @JDGrimes, [#254][gh-254])
|
1715 |
+
|
1716 |
+
- Fix a copy/paste error in schema doc.
|
1717 |
+
|
1718 |
+
(props @JDGrimes, [#253][gh-253])
|
1719 |
+
|
1720 |
+
- Correct reference link in example schema.
|
1721 |
+
|
1722 |
+
(props @danielbachhuber, [#258][gh-258])
|
1723 |
+
|
1724 |
+
- Add missing post formats to post schema documentation.
|
1725 |
+
|
1726 |
+
(props @danielbachhuber, [#260][gh-260])
|
1727 |
+
|
1728 |
+
- Ensure we always use "public" on public methods.
|
1729 |
+
|
1730 |
+
(props @danielbachhuber, [#268][gh-268])
|
1731 |
+
|
1732 |
+
- Ensure we don't cause a PHP error if a post does not have revisions.
|
1733 |
+
|
1734 |
+
(props @rmccue, [#227][gh-227])
|
1735 |
+
|
1736 |
+
- Add note to where upload_files cap comes from
|
1737 |
+
|
1738 |
+
(props @pkevan, [#282][gh-282])
|
1739 |
+
|
1740 |
+
- Add handling of `sticky` property when creating or editing posts.
|
1741 |
+
|
1742 |
+
(props @rachelbaker, [#218][gh-218])
|
1743 |
+
|
1744 |
+
- Update post route endpoint docs to include details on `post_meta` handling.
|
1745 |
+
|
1746 |
+
(props @rachelbaker, [#213][gh-213])
|
1747 |
+
|
1748 |
+
- Update main readme file to better describe the project.
|
1749 |
+
|
1750 |
+
(props @rmccue, [#303][gh-303])
|
1751 |
+
|
1752 |
+
- Fix `--data-binary` cURL option in documentation
|
1753 |
+
|
1754 |
+
(props @Pezzab, @rachelbaker, @rmccue, [#283][gh-283], [#304][gh-304])
|
1755 |
+
|
1756 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/1.0...1.1)
|
1757 |
+
|
1758 |
+
[gh-99]: https://github.com/WP-API/WP-API/issues/99
|
1759 |
+
[gh-104]: https://github.com/WP-API/WP-API/issues/104
|
1760 |
+
[gh-179]: https://github.com/WP-API/WP-API/issues/179
|
1761 |
+
[gh-185]: https://github.com/WP-API/WP-API/issues/185
|
1762 |
+
[gh-198]: https://github.com/WP-API/WP-API/issues/198
|
1763 |
+
[gh-211]: https://github.com/WP-API/WP-API/issues/211
|
1764 |
+
[gh-212]: https://github.com/WP-API/WP-API/issues/212
|
1765 |
+
[gh-213]: https://github.com/WP-API/WP-API/issues/213
|
1766 |
+
[gh-214]: https://github.com/WP-API/WP-API/issues/214
|
1767 |
+
[gh-218]: https://github.com/WP-API/WP-API/issues/218
|
1768 |
+
[gh-221]: https://github.com/WP-API/WP-API/issues/221
|
1769 |
+
[gh-225]: https://github.com/WP-API/WP-API/issues/225
|
1770 |
+
[gh-225]: https://github.com/WP-API/WP-API/issues/225
|
1771 |
+
[gh-226]: https://github.com/WP-API/WP-API/issues/226
|
1772 |
+
[gh-226]: https://github.com/WP-API/WP-API/issues/226
|
1773 |
+
[gh-227]: https://github.com/WP-API/WP-API/issues/227
|
1774 |
+
[gh-228]: https://github.com/WP-API/WP-API/issues/228
|
1775 |
+
[gh-229]: https://github.com/WP-API/WP-API/issues/229
|
1776 |
+
[gh-230]: https://github.com/WP-API/WP-API/issues/230
|
1777 |
+
[gh-231]: https://github.com/WP-API/WP-API/issues/231
|
1778 |
+
[gh-235]: https://github.com/WP-API/WP-API/issues/235
|
1779 |
+
[gh-236]: https://github.com/WP-API/WP-API/issues/236
|
1780 |
+
[gh-240]: https://github.com/WP-API/WP-API/issues/240
|
1781 |
+
[gh-242]: https://github.com/WP-API/WP-API/issues/242
|
1782 |
+
[gh-243]: https://github.com/WP-API/WP-API/issues/243
|
1783 |
+
[gh-244]: https://github.com/WP-API/WP-API/issues/244
|
1784 |
+
[gh-251]: https://github.com/WP-API/WP-API/issues/251
|
1785 |
+
[gh-253]: https://github.com/WP-API/WP-API/issues/253
|
1786 |
+
[gh-254]: https://github.com/WP-API/WP-API/issues/254
|
1787 |
+
[gh-255]: https://github.com/WP-API/WP-API/issues/255
|
1788 |
+
[gh-258]: https://github.com/WP-API/WP-API/issues/258
|
1789 |
+
[gh-260]: https://github.com/WP-API/WP-API/issues/260
|
1790 |
+
[gh-266]: https://github.com/WP-API/WP-API/issues/266
|
1791 |
+
[gh-268]: https://github.com/WP-API/WP-API/issues/268
|
1792 |
+
[gh-275]: https://github.com/WP-API/WP-API/issues/275
|
1793 |
+
[gh-276]: https://github.com/WP-API/WP-API/issues/276
|
1794 |
+
[gh-277]: https://github.com/WP-API/WP-API/issues/277
|
1795 |
+
[gh-278]: https://github.com/WP-API/WP-API/issues/278
|
1796 |
+
[gh-279]: https://github.com/WP-API/WP-API/issues/279
|
1797 |
+
[gh-282]: https://github.com/WP-API/WP-API/issues/282
|
1798 |
+
[gh-283]: https://github.com/WP-API/WP-API/issues/283
|
1799 |
+
[gh-284]: https://github.com/WP-API/WP-API/issues/284
|
1800 |
+
[gh-285]: https://github.com/WP-API/WP-API/issues/285
|
1801 |
+
[gh-286]: https://github.com/WP-API/WP-API/issues/286
|
1802 |
+
[gh-288]: https://github.com/WP-API/WP-API/issues/288
|
1803 |
+
[gh-289]: https://github.com/WP-API/WP-API/issues/289
|
1804 |
+
[gh-290]: https://github.com/WP-API/WP-API/issues/290
|
1805 |
+
[gh-296]: https://github.com/WP-API/WP-API/issues/296
|
1806 |
+
[gh-298]: https://github.com/WP-API/WP-API/issues/298
|
1807 |
+
[gh-303]: https://github.com/WP-API/WP-API/issues/303
|
1808 |
+
[gh-304]: https://github.com/WP-API/WP-API/issues/304
|
1809 |
+
[gh-313]: https://github.com/WP-API/WP-API/issues/313
|
1810 |
+
|
1811 |
+
## 1.0
|
1812 |
+
|
1813 |
+
- Add user endpoints.
|
1814 |
+
|
1815 |
+
Creating, reading, updating and deleting users and their data is now possible
|
1816 |
+
by using the `/users` endpoints. `/users/me` can be used to determine the
|
1817 |
+
current user, and returns a 401 status for non-logged in users.
|
1818 |
+
|
1819 |
+
Note that the format of post authors has changed, as it is now an embedded
|
1820 |
+
User entity. This should not break backwards compatibility.
|
1821 |
+
|
1822 |
+
Custom post types gain this ability automatically.
|
1823 |
+
|
1824 |
+
(props @tobych, @rmccue, [#20][gh-20], [#146][gh-146])
|
1825 |
+
|
1826 |
+
- Add post meta endpoints.
|
1827 |
+
|
1828 |
+
Creating, reading, updating and deleting post meta is now possible by using
|
1829 |
+
the `/posts/<id>/meta` endpoints. Post meta is now correctly embedded into
|
1830 |
+
Post entities.
|
1831 |
+
|
1832 |
+
Meta can be updated via the Post entity (e.g. `PUT` to `/posts/<id>`) or via
|
1833 |
+
the entity itself at `/posts/<id>/meta/<mid>`. Meta deletion must be done via
|
1834 |
+
a `DELETE` request to the latter.
|
1835 |
+
|
1836 |
+
Only non-protected and non-serialized meta can be accessed or manipulated via
|
1837 |
+
the API. This is not predicted to change in the future; clients wishing to
|
1838 |
+
access this data should consider alternative approaches.
|
1839 |
+
|
1840 |
+
Custom post types do not currently gain this ability automatically.
|
1841 |
+
|
1842 |
+
(props @attitude, @alisspers, @rachelbaker, @rmccue, @tlovett1, @tobych,
|
1843 |
+
@zedejose, [#68][gh-68], [#168][gh-168], [#189][gh-189], [#207][gh-207])
|
1844 |
+
|
1845 |
+
- Add endpoint for deleting a single comment.
|
1846 |
+
|
1847 |
+
Clients can now send a `DELETE` request to comment routes to delete
|
1848 |
+
the comment.
|
1849 |
+
|
1850 |
+
Custom post types supporting comments will gain this ability automatically.
|
1851 |
+
|
1852 |
+
(props @tlovett1, @rmccue, [#178][gh-178], [#191][gh-191])
|
1853 |
+
|
1854 |
+
- Add endpoint for post revisions.
|
1855 |
+
|
1856 |
+
Post revisions are now available at `/posts/<id>/revisions`, and are linked in
|
1857 |
+
the `meta.links.version-history` key of post entities.
|
1858 |
+
|
1859 |
+
Custom post types supporting revisions will gain this ability automatically.
|
1860 |
+
|
1861 |
+
(props @tlovett1, [#193][gh-193])
|
1862 |
+
|
1863 |
+
- Respond to requests without depending on pretty permalink settings.
|
1864 |
+
|
1865 |
+
For sites without pretty permalinks enabled, the API is now available from
|
1866 |
+
`?json_route=/`. Clients should check for this via the autodiscovery methods
|
1867 |
+
(Link header or RSD).
|
1868 |
+
|
1869 |
+
(props @rmccue, [#69][gh-69], [#138][gh-138])
|
1870 |
+
|
1871 |
+
- Add register post type argument.
|
1872 |
+
|
1873 |
+
Post types can now indicate their availability via the API using the
|
1874 |
+
`show_in_json` argument passed to `register_post_type`. This value defaults to
|
1875 |
+
the `publicly_queryable` argument (which itself defaults to the
|
1876 |
+
`public` argument).
|
1877 |
+
|
1878 |
+
(props @iandunn, @rmccue, [#145][gh-145])
|
1879 |
+
|
1880 |
+
- Remove basic authentication handler.
|
1881 |
+
|
1882 |
+
**This breaks backwards compatibility** for clients using Basic
|
1883 |
+
authentication. Clients are encouraged to switch to using [OAuth
|
1884 |
+
authentication][OAuth1]. The [Basic Authentication plugin][Basic-Auth] can be
|
1885 |
+
installed for backwards compatibility and local development, however should
|
1886 |
+
not be used in production.
|
1887 |
+
|
1888 |
+
(props @rmccue, [#37][gh-37], [#152][gh-152])
|
1889 |
+
|
1890 |
+
- Require nonces for cookie-based authentication.
|
1891 |
+
|
1892 |
+
**This breaks backwards compatibility** and requires any clients using cookie
|
1893 |
+
authentication to also send a nonce with the request. The built-in Javascript
|
1894 |
+
API automatically handles this.
|
1895 |
+
|
1896 |
+
(props @rmccue, [#177][gh-177], [#180][gh-180])
|
1897 |
+
|
1898 |
+
- Clean up deprecated methods/functions.
|
1899 |
+
|
1900 |
+
Functions and methods previously deprecated in 0.8/0.9 have now been removed.
|
1901 |
+
Future deprecations will take place in the same manner as WordPress core.
|
1902 |
+
|
1903 |
+
**This breaks backwards compatibility**, however these were marked as
|
1904 |
+
deprecated in previous releases.
|
1905 |
+
|
1906 |
+
(props @rmccue, [#187][gh-187])
|
1907 |
+
|
1908 |
+
- Only expose meta on 'edit' context as a temporary workaround.
|
1909 |
+
|
1910 |
+
Privacy concerns around exposing meta to all users necessitate this change.
|
1911 |
+
|
1912 |
+
**This breaks backwards compatibility** as post meta data is no longer
|
1913 |
+
available to all users. Clients wishing to access this data should
|
1914 |
+
authenticate and use the `edit` context.
|
1915 |
+
|
1916 |
+
(props @iandunn, @rmccue, [#135][gh-135])
|
1917 |
+
|
1918 |
+
- Add `json_ensure_response` function to ensure either a
|
1919 |
+
`WP_JSON_ResponseInterface` or a `WP_Error` object is returned.
|
1920 |
+
|
1921 |
+
When extending the API, the `json_ensure_response` function can be used to
|
1922 |
+
ensure that any raw data returned is wrapped with a `WP_JSON_Response` object.
|
1923 |
+
This allows using `get_status`/`get_data` easily, however `WP_Error` must
|
1924 |
+
still be checked via `is_wp_error`.
|
1925 |
+
|
1926 |
+
(props @rmccue, [#151][gh-151], [#154][gh-154])
|
1927 |
+
|
1928 |
+
- Use version option to check on init if rewrite rules should be flushed.
|
1929 |
+
|
1930 |
+
Rewrite rules on multisite are now flushed via an init hook, rather than
|
1931 |
+
switching to each site on activation.
|
1932 |
+
|
1933 |
+
(props @rachelbaker, [#149][gh-149])
|
1934 |
+
|
1935 |
+
- Fix typo in schema docs
|
1936 |
+
|
1937 |
+
(props @codebykat, [#132][gh-132])
|
1938 |
+
|
1939 |
+
- Add check for valid JSON data before using to avoid parameter overwrite.
|
1940 |
+
|
1941 |
+
When passing data to an endpoint that accepts JSON data, the data will now be
|
1942 |
+
validated before passing to the endpoint.
|
1943 |
+
|
1944 |
+
(props @rachelbaker, @rmccue, [#133][gh-133])
|
1945 |
+
|
1946 |
+
- Add authentication property to site index.
|
1947 |
+
|
1948 |
+
(props @rmccue, [#131][gh-131])
|
1949 |
+
|
1950 |
+
- Move the test helper to a subdirectory.
|
1951 |
+
|
1952 |
+
The plugin will now no longer prompt for updates due to the helper.
|
1953 |
+
|
1954 |
+
(props @rmccue, [#127][gh-127])
|
1955 |
+
|
1956 |
+
- Include post ID with `json_prepare_meta` filter.
|
1957 |
+
|
1958 |
+
(props @rmccue, [#137][gh-137])
|
1959 |
+
|
1960 |
+
- Corrected parameter names in x-form examples in docs.
|
1961 |
+
|
1962 |
+
(props @rachelbaker, [#134][gh-134])
|
1963 |
+
|
1964 |
+
- Pass `WP_JSON_Server` instance to `json_serve_request`.
|
1965 |
+
|
1966 |
+
(props @alisspers, @rmccue, [#61][gh-61], [#139][gh-139])
|
1967 |
+
|
1968 |
+
- Don't use deprecated function in `WP_JSON_Posts::edit_post()`
|
1969 |
+
|
1970 |
+
(props @rachelbaker, [#150][gh-150])
|
1971 |
+
|
1972 |
+
- Pass post ID to `json_insert_post` action during both insert and update.
|
1973 |
+
|
1974 |
+
(props @cmmarslender, [#148][gh-148])
|
1975 |
+
|
1976 |
+
- Add descriptions to taxonomy term data.
|
1977 |
+
|
1978 |
+
(props @pushred, [#111][gh-111])
|
1979 |
+
|
1980 |
+
- Ensure we handle raw data passed to the API.
|
1981 |
+
|
1982 |
+
(props @tlovett1, @rmccue, [#91][gh-91], [#155][gh-155])
|
1983 |
+
|
1984 |
+
- Remove unused `prepare_author` method from `WP_JSON_Posts` class.
|
1985 |
+
|
1986 |
+
(props @rachelbaker, [#165][gh-165])
|
1987 |
+
|
1988 |
+
- Add multiple post type support to get_posts method.
|
1989 |
+
|
1990 |
+
(props @rmccue, [#142][gh-142], [#163][gh-163])
|
1991 |
+
|
1992 |
+
- Return `WP_Error` in `WP_JSON_Posts::get_comment` for invalid comments.
|
1993 |
+
|
1994 |
+
(props @tlovett1, [#166][gh-166], [#171][gh-171])
|
1995 |
+
|
1996 |
+
- Update getting started documentation.
|
1997 |
+
|
1998 |
+
(props @rmccue, [#176][gh-176])
|
1999 |
+
|
2000 |
+
- Improve and clarify "array" input syntax documentation.
|
2001 |
+
|
2002 |
+
(props @rmccue, [#140][gh-140], [#175][gh-175])
|
2003 |
+
|
2004 |
+
- Update post routes documentation.
|
2005 |
+
|
2006 |
+
(props @rmccue, [#172][gh-172], [#174][gh-174])
|
2007 |
+
|
2008 |
+
- Add documentation for user endpoints.
|
2009 |
+
|
2010 |
+
(props @rachelbaker, @rmccue, [#158][gh-158])
|
2011 |
+
|
2012 |
+
- Add permalink settings step to Quick Setup instructions.
|
2013 |
+
|
2014 |
+
(props @kadamwhite, [#183][gh-183])
|
2015 |
+
|
2016 |
+
- Update taxonomy collection to return indexed array.
|
2017 |
+
|
2018 |
+
(props @mattheu, [#184][gh-184])
|
2019 |
+
|
2020 |
+
- Remove placeholder endpoints.
|
2021 |
+
|
2022 |
+
(props @rmccue, [#161][gh-161], [#192][gh-192])
|
2023 |
+
|
2024 |
+
- Fix issues with embedded attachments.
|
2025 |
+
|
2026 |
+
Checks that the post supports attachment data before adding it, and ensures we
|
2027 |
+
don't embed entities many layers deep.
|
2028 |
+
|
2029 |
+
(props @rmccue, [#194][gh-194])
|
2030 |
+
|
2031 |
+
- Change post parent preparation context to embed.
|
2032 |
+
|
2033 |
+
(props @rmccue, [#195][gh-195])
|
2034 |
+
|
2035 |
+
- Change server meta links to reference the WP-API organization GitHub repo.
|
2036 |
+
|
2037 |
+
(props @rachelbaker, [#208][gh-208])
|
2038 |
+
|
2039 |
+
- Fix plugin tests
|
2040 |
+
|
2041 |
+
(props @rmccue, [#215][gh-215])
|
2042 |
+
|
2043 |
+
- Check for errors with invalid dates and remove duplicate date parsing
|
2044 |
+
methods.
|
2045 |
+
|
2046 |
+
(props @rachelbaker, @rmccue, [#216][gh-216], [#219][gh-219])
|
2047 |
+
|
2048 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/0.9...1.0)
|
2049 |
+
|
2050 |
+
[OAuth1]: https://github.com/WP-API/OAuth1
|
2051 |
+
[Basic-Auth]: https://github.com/WP-API/Basic-Auth
|
2052 |
+
[gh-20]: https://github.com/WP-API/WP-API/issues/20
|
2053 |
+
[gh-37]: https://github.com/WP-API/WP-API/issues/37
|
2054 |
+
[gh-61]: https://github.com/WP-API/WP-API/issues/61
|
2055 |
+
[gh-68]: https://github.com/WP-API/WP-API/issues/68
|
2056 |
+
[gh-69]: https://github.com/WP-API/WP-API/issues/69
|
2057 |
+
[gh-91]: https://github.com/WP-API/WP-API/issues/91
|
2058 |
+
[gh-111]: https://github.com/WP-API/WP-API/issues/111
|
2059 |
+
[gh-127]: https://github.com/WP-API/WP-API/issues/127
|
2060 |
+
[gh-131]: https://github.com/WP-API/WP-API/issues/131
|
2061 |
+
[gh-132]: https://github.com/WP-API/WP-API/issues/132
|
2062 |
+
[gh-133]: https://github.com/WP-API/WP-API/issues/133
|
2063 |
+
[gh-134]: https://github.com/WP-API/WP-API/issues/134
|
2064 |
+
[gh-135]: https://github.com/WP-API/WP-API/issues/135
|
2065 |
+
[gh-137]: https://github.com/WP-API/WP-API/issues/137
|
2066 |
+
[gh-138]: https://github.com/WP-API/WP-API/issues/138
|
2067 |
+
[gh-139]: https://github.com/WP-API/WP-API/issues/139
|
2068 |
+
[gh-140]: https://github.com/WP-API/WP-API/issues/140
|
2069 |
+
[gh-142]: https://github.com/WP-API/WP-API/issues/142
|
2070 |
+
[gh-145]: https://github.com/WP-API/WP-API/issues/145
|
2071 |
+
[gh-146]: https://github.com/WP-API/WP-API/issues/146
|
2072 |
+
[gh-148]: https://github.com/WP-API/WP-API/issues/148
|
2073 |
+
[gh-149]: https://github.com/WP-API/WP-API/issues/149
|
2074 |
+
[gh-150]: https://github.com/WP-API/WP-API/issues/150
|
2075 |
+
[gh-151]: https://github.com/WP-API/WP-API/issues/151
|
2076 |
+
[gh-152]: https://github.com/WP-API/WP-API/issues/152
|
2077 |
+
[gh-154]: https://github.com/WP-API/WP-API/issues/154
|
2078 |
+
[gh-155]: https://github.com/WP-API/WP-API/issues/155
|
2079 |
+
[gh-158]: https://github.com/WP-API/WP-API/issues/158
|
2080 |
+
[gh-161]: https://github.com/WP-API/WP-API/issues/161
|
2081 |
+
[gh-163]: https://github.com/WP-API/WP-API/issues/163
|
2082 |
+
[gh-165]: https://github.com/WP-API/WP-API/issues/165
|
2083 |
+
[gh-166]: https://github.com/WP-API/WP-API/issues/166
|
2084 |
+
[gh-168]: https://github.com/WP-API/WP-API/issues/168
|
2085 |
+
[gh-171]: https://github.com/WP-API/WP-API/issues/171
|
2086 |
+
[gh-172]: https://github.com/WP-API/WP-API/issues/172
|
2087 |
+
[gh-174]: https://github.com/WP-API/WP-API/issues/174
|
2088 |
+
[gh-175]: https://github.com/WP-API/WP-API/issues/175
|
2089 |
+
[gh-176]: https://github.com/WP-API/WP-API/issues/176
|
2090 |
+
[gh-177]: https://github.com/WP-API/WP-API/issues/177
|
2091 |
+
[gh-178]: https://github.com/WP-API/WP-API/issues/178
|
2092 |
+
[gh-180]: https://github.com/WP-API/WP-API/issues/180
|
2093 |
+
[gh-183]: https://github.com/WP-API/WP-API/issues/183
|
2094 |
+
[gh-184]: https://github.com/WP-API/WP-API/issues/184
|
2095 |
+
[gh-187]: https://github.com/WP-API/WP-API/issues/187
|
2096 |
+
[gh-189]: https://github.com/WP-API/WP-API/issues/189
|
2097 |
+
[gh-191]: https://github.com/WP-API/WP-API/issues/191
|
2098 |
+
[gh-192]: https://github.com/WP-API/WP-API/issues/192
|
2099 |
+
[gh-193]: https://github.com/WP-API/WP-API/issues/193
|
2100 |
+
[gh-194]: https://github.com/WP-API/WP-API/issues/194
|
2101 |
+
[gh-195]: https://github.com/WP-API/WP-API/issues/195
|
2102 |
+
[gh-207]: https://github.com/WP-API/WP-API/issues/207
|
2103 |
+
[gh-208]: https://github.com/WP-API/WP-API/issues/208
|
2104 |
+
[gh-215]: https://github.com/WP-API/WP-API/issues/215
|
2105 |
+
[gh-216]: https://github.com/WP-API/WP-API/issues/216
|
2106 |
+
[gh-219]: https://github.com/WP-API/WP-API/issues/219
|
2107 |
+
|
2108 |
+
## 0.9
|
2109 |
+
|
2110 |
+
- Move from `wp-json.php/` to `wp-json/`
|
2111 |
+
|
2112 |
+
**This breaks backwards compatibility** and requires any clients to now use
|
2113 |
+
`wp-json/`, or preferably the new RSD/Link headers.
|
2114 |
+
|
2115 |
+
(props @rmccue, @matrixik, [#46][gh-46], [#96][gh-96], [#106][gh-106])
|
2116 |
+
|
2117 |
+
- Move filter registration out of CPT constructor. CPT subclasses now require
|
2118 |
+
you to call `$myobject->register_filters()`, in order to move global state out
|
2119 |
+
of the constructor.
|
2120 |
+
|
2121 |
+
**This breaks backwards compatibility** and requires any subclassing to now
|
2122 |
+
call `$myobject->register_filters()`
|
2123 |
+
|
2124 |
+
(props @rmccue, @thenbrent, [#42][gh-42], [#126][gh-126])
|
2125 |
+
|
2126 |
+
- Introduce Response/ResponseInterface
|
2127 |
+
|
2128 |
+
Endpoints that need to set headers or response codes should now return a
|
2129 |
+
`WP_JSON_Response` rather than using the server methods.
|
2130 |
+
`WP_JSON_ResponseInterface` may also be used for more flexible use of the
|
2131 |
+
response methods.
|
2132 |
+
|
2133 |
+
**Deprecation warning:** Calling `WP_JSON_Server::header`,
|
2134 |
+
`WP_JSON_Server::link_header` and `WP_JSON_Server::query_navigation_headers`
|
2135 |
+
is now deprecated. This will be removed in 1.0.
|
2136 |
+
|
2137 |
+
(props @rmccue, [#33][gh-33])
|
2138 |
+
|
2139 |
+
- Change all semiCamelCase names to underscore_case.
|
2140 |
+
|
2141 |
+
**Deprecation warning**: Any calls to semiCamelCase methods require any
|
2142 |
+
subclassing to update method references. This will be removed in 1.0.
|
2143 |
+
|
2144 |
+
(props @osiux, [#36][gh-36], [#82][gh-82])
|
2145 |
+
|
2146 |
+
- Add multisite compatibility. If the plugin is network activated, the plugin is
|
2147 |
+
now activated once-per-site, so `wp-json/` is always site-local.
|
2148 |
+
|
2149 |
+
(props @rachelbaker, [#48][gh-48], [#49][gh-49])
|
2150 |
+
|
2151 |
+
- Add RSD and Link headers for discovery
|
2152 |
+
|
2153 |
+
(props @rmccue, [#40][gh-40])
|
2154 |
+
|
2155 |
+
- WP_JSON_Posts->prepare_author() now verifies the `$user` object is set.
|
2156 |
+
|
2157 |
+
(props @rachelbaker, [#51][gh-51], [#54][gh-54])
|
2158 |
+
|
2159 |
+
- Added unit testing framework. Currently only a smaller number of tests, but we
|
2160 |
+
plan to increase this significantly as soon as possible.
|
2161 |
+
|
2162 |
+
(props @tierra, @osiux, [#65][gh-65], [#76][gh-76], [#84][gh-84])
|
2163 |
+
|
2164 |
+
- Link collection filtering docs to URL formatting guide.
|
2165 |
+
|
2166 |
+
(props @kadamwhite, [#74][gh-74])
|
2167 |
+
|
2168 |
+
- Remove hardcoded `/pages` references from `WP_JSON_Pages`
|
2169 |
+
|
2170 |
+
(props @rmccue, @thenbrent, [#28][gh-28], [#78][gh-78])
|
2171 |
+
|
2172 |
+
- Fix compatibility with `DateTime::createFromFormat` on PHP 5.2
|
2173 |
+
|
2174 |
+
(props @osiux, [#52][gh-52], [#79][gh-79])
|
2175 |
+
|
2176 |
+
- Document that `WP_JSON_CustomPostType::__construct()` requires a param of type
|
2177 |
+
`WP_JSON_ResponseHandler`.
|
2178 |
+
|
2179 |
+
(props @tlovett1, [#88][gh-88])
|
2180 |
+
|
2181 |
+
- Add timezone parameter to WP_JSON_DateTime::createFromFormat()
|
2182 |
+
|
2183 |
+
(props @royboy789, @rachelbaker, [#85][gh-85], [#87][gh-87])
|
2184 |
+
|
2185 |
+
- Remove IXR references. `IXR_Error` is no longer accepted as a return value.
|
2186 |
+
|
2187 |
+
**This breaks backwards compatibility** and requires anyone returning
|
2188 |
+
`IXR_Error` objects to now return `WP_Error` or `WP_JSON_ResponseInterface`
|
2189 |
+
objects.
|
2190 |
+
|
2191 |
+
(props @rmccue, [#50][gh-50], [#77][gh-77])
|
2192 |
+
|
2193 |
+
- Fix bugs with attaching featured images to posts:
|
2194 |
+
- `WP_JSON_Media::attachThumbnail()` should do nothing if `$update` is false
|
2195 |
+
without a post ID
|
2196 |
+
- The post ID must be fetched from the `$post` array.
|
2197 |
+
|
2198 |
+
(props @Webbgaraget, [#55][gh-55])
|
2199 |
+
|
2200 |
+
- Don't declare `jsonSerialize` on ResponseInterface
|
2201 |
+
|
2202 |
+
(props @rmccue, [#97][gh-97])
|
2203 |
+
|
2204 |
+
- Allow JSON post creation/update for `WP_JSON_CustomPostType`
|
2205 |
+
|
2206 |
+
(props @tlovett1, [#90][gh-90], [#108][gh-108])
|
2207 |
+
|
2208 |
+
- Return null if post doesn't have an excerpt
|
2209 |
+
|
2210 |
+
(props @rachelbacker, [#72][gh-72])
|
2211 |
+
|
2212 |
+
- Fix link to issue tracker in README
|
2213 |
+
|
2214 |
+
(props @rmccue, @tobych, [#125][gh-125])
|
2215 |
+
|
2216 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/0.8...0.9)
|
2217 |
+
|
2218 |
+
[gh-28]: https://github.com/WP-API/WP-API/issues/28
|
2219 |
+
[gh-33]: https://github.com/WP-API/WP-API/issues/33
|
2220 |
+
[gh-36]: https://github.com/WP-API/WP-API/issues/36
|
2221 |
+
[gh-40]: https://github.com/WP-API/WP-API/issues/40
|
2222 |
+
[gh-42]: https://github.com/WP-API/WP-API/issues/42
|
2223 |
+
[gh-46]: https://github.com/WP-API/WP-API/issues/46
|
2224 |
+
[gh-48]: https://github.com/WP-API/WP-API/issues/48
|
2225 |
+
[gh-49]: https://github.com/WP-API/WP-API/issues/49
|
2226 |
+
[gh-50]: https://github.com/WP-API/WP-API/issues/50
|
2227 |
+
[gh-51]: https://github.com/WP-API/WP-API/issues/51
|
2228 |
+
[gh-52]: https://github.com/WP-API/WP-API/issues/52
|
2229 |
+
[gh-54]: https://github.com/WP-API/WP-API/issues/54
|
2230 |
+
[gh-55]: https://github.com/WP-API/WP-API/issues/55
|
2231 |
+
[gh-65]: https://github.com/WP-API/WP-API/issues/65
|
2232 |
+
[gh-72]: https://github.com/WP-API/WP-API/issues/72
|
2233 |
+
[gh-74]: https://github.com/WP-API/WP-API/issues/74
|
2234 |
+
[gh-76]: https://github.com/WP-API/WP-API/issues/76
|
2235 |
+
[gh-77]: https://github.com/WP-API/WP-API/issues/77
|
2236 |
+
[gh-78]: https://github.com/WP-API/WP-API/issues/78
|
2237 |
+
[gh-79]: https://github.com/WP-API/WP-API/issues/79
|
2238 |
+
[gh-82]: https://github.com/WP-API/WP-API/issues/82
|
2239 |
+
[gh-84]: https://github.com/WP-API/WP-API/issues/84
|
2240 |
+
[gh-85]: https://github.com/WP-API/WP-API/issues/85
|
2241 |
+
[gh-87]: https://github.com/WP-API/WP-API/issues/87
|
2242 |
+
[gh-88]: https://github.com/WP-API/WP-API/issues/88
|
2243 |
+
[gh-90]: https://github.com/WP-API/WP-API/issues/90
|
2244 |
+
[gh-96]: https://github.com/WP-API/WP-API/issues/96
|
2245 |
+
[gh-97]: https://github.com/WP-API/WP-API/issues/97
|
2246 |
+
[gh-106]: https://github.com/WP-API/WP-API/issues/106
|
2247 |
+
[gh-108]: https://github.com/WP-API/WP-API/issues/108
|
2248 |
+
[gh-125]: https://github.com/WP-API/WP-API/issues/125
|
2249 |
+
[gh-126]: https://github.com/WP-API/WP-API/issues/126
|
2250 |
+
|
2251 |
+
## 0.8
|
2252 |
+
- Add compatibility layer for JsonSerializable. You can now return arbitrary
|
2253 |
+
objects from endpoints and use the `jsonSerialize()` method to return the data
|
2254 |
+
to serialize instead of just using the properties of the object.
|
2255 |
+
|
2256 |
+
(props @rmccue, [#24][gh-24])
|
2257 |
+
|
2258 |
+
- Fix page parent links to use `/pages`
|
2259 |
+
|
2260 |
+
(props @thenbrent, [#27][gh-27])
|
2261 |
+
|
2262 |
+
- Remove redundant `WP_JSON_Pages::type_archive_link()` function
|
2263 |
+
|
2264 |
+
(props @thenbrent, [#29][gh-29])
|
2265 |
+
|
2266 |
+
- Removed unneeded executable bit on all files
|
2267 |
+
|
2268 |
+
(props @tierra, [#31][gh-31])
|
2269 |
+
|
2270 |
+
- Don't include the `featured_image` property for post types that don't
|
2271 |
+
support thumbnails
|
2272 |
+
|
2273 |
+
(props @phh, [#43][gh-43])
|
2274 |
+
|
2275 |
+
- Use `wp_json_server_before_serve` instead of `plugins_loaded` in the Extending
|
2276 |
+
documentation for plugins
|
2277 |
+
|
2278 |
+
(props @phh, [#43][gh-43])
|
2279 |
+
|
2280 |
+
- Parse the avatar URL from the `get_avatar()` function in core, allowing custom
|
2281 |
+
avatar implementations
|
2282 |
+
|
2283 |
+
(props @rachelbaker, [#47][gh-47], [#35][gh-35])
|
2284 |
+
|
2285 |
+
- Ensure that the author is set if passed
|
2286 |
+
|
2287 |
+
(props @kuchenundkakao, [#44][gh-44])
|
2288 |
+
|
2289 |
+
- Clarify the usage of `WP_JSON_CustomPostType` in plugins
|
2290 |
+
|
2291 |
+
(props @rmccue, [#45][gh-45])
|
2292 |
+
|
2293 |
+
- Ensure JSON disabled error messages are translated
|
2294 |
+
|
2295 |
+
(props @rmccue, [#38][gh-38])
|
2296 |
+
|
2297 |
+
- Remove extra "Link: " from link headers
|
2298 |
+
|
2299 |
+
(props @jmusal, [#56][gh-56], [#30][gh-30])
|
2300 |
+
|
2301 |
+
- Remove redundant `get_avatar` method in `WP_JSON_Posts`
|
2302 |
+
|
2303 |
+
(props @rachelbaker, [#35][gh-35])
|
2304 |
+
|
2305 |
+
- Rename `WP_JSON_Server::get_avatar()` to `WP_JSON_Server::get_avatar_url()`
|
2306 |
+
|
2307 |
+
(props @rachelbaker, [#35][gh-35])
|
2308 |
+
|
2309 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/0.7...0.8)
|
2310 |
+
|
2311 |
+
[gh-24]: https://github.com/WP-API/WP-API/issues/24
|
2312 |
+
[gh-27]: https://github.com/WP-API/WP-API/issues/27
|
2313 |
+
[gh-29]: https://github.com/WP-API/WP-API/issues/29
|
2314 |
+
[gh-30]: https://github.com/WP-API/WP-API/issues/30
|
2315 |
+
[gh-31]: https://github.com/WP-API/WP-API/issues/31
|
2316 |
+
[gh-35]: https://github.com/WP-API/WP-API/issues/35
|
2317 |
+
[gh-38]: https://github.com/WP-API/WP-API/issues/38
|
2318 |
+
[gh-43]: https://github.com/WP-API/WP-API/issues/43
|
2319 |
+
[gh-43]: https://github.com/WP-API/WP-API/issues/43
|
2320 |
+
[gh-44]: https://github.com/WP-API/WP-API/issues/44
|
2321 |
+
[gh-45]: https://github.com/WP-API/WP-API/issues/45
|
2322 |
+
[gh-47]: https://github.com/WP-API/WP-API/issues/47
|
2323 |
+
[gh-56]: https://github.com/WP-API/WP-API/issues/56
|
2324 |
+
|
2325 |
+
## 0.7
|
2326 |
+
- The response handler object is now passed into the endpoint objects via the
|
2327 |
+
constructor, allowing you to avoid excess global state where possible. It's
|
2328 |
+
recommended to use this where possible rather than the global object.
|
2329 |
+
|
2330 |
+
(props @rmccue, [#2][gh-2])
|
2331 |
+
|
2332 |
+
- Fix undefined variables and indices
|
2333 |
+
(props @pippinsplugins, [#5][gh-5])
|
2334 |
+
|
2335 |
+
- Correct call to deactivation hook
|
2336 |
+
(props @ericpedia, [#9][gh-9])
|
2337 |
+
|
2338 |
+
- Check metadata access correctly rather than always hiding for users without
|
2339 |
+
the `edit_post_meta` capability
|
2340 |
+
(props @kokarn, [#10][gh-10])
|
2341 |
+
|
2342 |
+
- Return all term metadata, rather than just the last one
|
2343 |
+
(props @afurculita, [#13][gh-13])
|
2344 |
+
|
2345 |
+
- Access post metadata from cache where possible - Note, this is a backwards
|
2346 |
+
compatibility break, as the format of the metadata has changed. This may
|
2347 |
+
change again in the near future, so don't rely on it until 1.0.
|
2348 |
+
(props @afurculita, [#14][gh-14])
|
2349 |
+
|
2350 |
+
- Add term_link to prepare_term
|
2351 |
+
(props @afurculita, [#15][gh-15])
|
2352 |
+
|
2353 |
+
- Fix hardcoded `/pages` references in `WP_JSON_CustomPostType`
|
2354 |
+
(props @thenbrent, [#26][gh-26])
|
2355 |
+
|
2356 |
+
- Sanitize headers for newlines
|
2357 |
+
(props @kokarn, [#7][gh-7])
|
2358 |
+
|
2359 |
+
- Register rewrite rules during plugin activation
|
2360 |
+
(props @pippinsplugins, [#17][gh-17])
|
2361 |
+
|
2362 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/0.6...0.7)
|
2363 |
+
|
2364 |
+
[gh-2]: https://github.com/WP-API/WP-API/issues/2
|
2365 |
+
[gh-5]: https://github.com/WP-API/WP-API/issues/5
|
2366 |
+
[gh-7]: https://github.com/WP-API/WP-API/issues/7
|
2367 |
+
[gh-9]: https://github.com/WP-API/WP-API/issues/9
|
2368 |
+
[gh-10]: https://github.com/WP-API/WP-API/issues/10
|
2369 |
+
[gh-13]: https://github.com/WP-API/WP-API/issues/13
|
2370 |
+
[gh-14]: https://github.com/WP-API/WP-API/issues/14
|
2371 |
+
[gh-15]: https://github.com/WP-API/WP-API/issues/15
|
2372 |
+
[gh-17]: https://github.com/WP-API/WP-API/issues/17
|
2373 |
+
[gh-26]: https://github.com/WP-API/WP-API/issues/26
|
2374 |
+
|
2375 |
+
## 0.6
|
2376 |
+
- Huge documentation update - Guides on getting started and extending the API
|
2377 |
+
are [now available for your perusal][docs]
|
2378 |
+
- Add generic CPT class - Plugins are now encouraged to extend
|
2379 |
+
`WP_JSON_CustomPostType` and get free hooking for common actions. This
|
2380 |
+
removes most of the boilerplate that you needed to write for new CPT-based
|
2381 |
+
routes and endpoints ([#380][])
|
2382 |
+
- Use defined filter priorities for endpoint registration - It's now easier to
|
2383 |
+
inject your own endpoints at a defined point
|
2384 |
+
- Update the schema - Now includes documentation on the Media entity, plus more
|
2385 |
+
([#264][])
|
2386 |
+
- Add better taxonomy support - You can now query for taxonomies and terms
|
2387 |
+
directly. The routes here might seem strange
|
2388 |
+
(`/posts/types/post/taxonomies/category` for example), but the intention is
|
2389 |
+
to [future-proof them](http://make.wordpress.org/core/2013/07/28/potential-roadmap-for-taxonomy-meta-and-post-relationships/)
|
2390 |
+
as much as possible([#275][])
|
2391 |
+
- Ensure the JSON URL is relative to the home URL ([#375][])
|
2392 |
+
- Check all date formats for If-Unmodified-Since ([#378][])
|
2393 |
+
- Register the correct URL for the JS library ([#376][])
|
2394 |
+
- Correct the usage of meta links ([#379][])
|
2395 |
+
- Add filters for post type and post status data ([#380][])
|
2396 |
+
- Separate parent post and parent comment relation ([#330][]()
|
2397 |
+
|
2398 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/0.5...0.6)
|
2399 |
+
|
2400 |
+
[docs]: https://github.com/rmccue/WP-API/tree/master/docs
|
2401 |
+
|
2402 |
+
[#264]: https://gsoc.trac.wordpress.org/ticket/264
|
2403 |
+
[#275]: https://gsoc.trac.wordpress.org/ticket/275
|
2404 |
+
[#330]: https://gsoc.trac.wordpress.org/ticket/330
|
2405 |
+
[#375]: https://gsoc.trac.wordpress.org/ticket/375
|
2406 |
+
[#376]: https://gsoc.trac.wordpress.org/ticket/376
|
2407 |
+
[#378]: https://gsoc.trac.wordpress.org/ticket/378
|
2408 |
+
[#379]: https://gsoc.trac.wordpress.org/ticket/379
|
2409 |
+
[#380]: https://gsoc.trac.wordpress.org/ticket/380
|
2410 |
+
|
2411 |
+
|
2412 |
+
## 0.5
|
2413 |
+
- Add support for media - This has been a long time coming, and it's finally at
|
2414 |
+
a point where I'm happy to push it out. Good luck. ([#272][])
|
2415 |
+
- Separate the post-related endpoints - Post-related endpoints are now located
|
2416 |
+
in the `WP_JSON_Posts` class. When implementing custom post type support,
|
2417 |
+
it's recommended to subclass this.
|
2418 |
+
|
2419 |
+
The various types are now also only registered via hooks, rather than
|
2420 |
+
directly in the server class, which should make it easier to override them
|
2421 |
+
as well ([#348][])
|
2422 |
+
- Add page support - This is a good base if you're looking to create your own
|
2423 |
+
custom post type support ([#271][])
|
2424 |
+
- Switch from fields to context - Rather than passing in a list of fields that
|
2425 |
+
you want, you can now pass in a context (usually `view` or `edit`)
|
2426 |
+
([#328][]).
|
2427 |
+
- Always send headers via the server handler - Endpoints are now completely
|
2428 |
+
separate from the request, so the server class can now be used for
|
2429 |
+
non-HTTP/JSON handlers if needed ([#293][])
|
2430 |
+
- Use better error codes for disabled features ([#338][])
|
2431 |
+
- Send `X-WP-Total` and `X-WP-TotalPages` headers for information on
|
2432 |
+
post/pagination counts ([#266][])
|
2433 |
+
|
2434 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/0.4...0.5)
|
2435 |
+
|
2436 |
+
[#266]: https://gsoc.trac.wordpress.org/ticket/266
|
2437 |
+
[#271]: https://gsoc.trac.wordpress.org/ticket/271
|
2438 |
+
[#272]: https://gsoc.trac.wordpress.org/ticket/272
|
2439 |
+
[#293]: https://gsoc.trac.wordpress.org/ticket/293
|
2440 |
+
[#328]: https://gsoc.trac.wordpress.org/ticket/328
|
2441 |
+
[#338]: https://gsoc.trac.wordpress.org/ticket/338
|
2442 |
+
[#348]: https://gsoc.trac.wordpress.org/ticket/348
|
2443 |
+
|
2444 |
+
|
2445 |
+
## 0.4
|
2446 |
+
- Add Backbone-based models and collections - These are available to your code
|
2447 |
+
by declaring a dependency on `wp-api` ([#270][])
|
2448 |
+
- Check `json_route` before using it ([#336][])
|
2449 |
+
- Conditionally load classes ([#337][])
|
2450 |
+
- Add additional test helper plugin - Provides code coverage as needed to the
|
2451 |
+
API client tests. Currently unused. ([#269][])
|
2452 |
+
- Move `json_url()` and `get_json_url()` to `plugin.php` - This allows using
|
2453 |
+
both outside of the API itself ([#343][])
|
2454 |
+
- `getPost(0)` now returns an error rather than the latest post ([#344][])
|
2455 |
+
|
2456 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/0.3...0.4)
|
2457 |
+
|
2458 |
+
[#269]: https://gsoc.trac.wordpress.org/ticket/269
|
2459 |
+
[#270]: https://gsoc.trac.wordpress.org/ticket/270
|
2460 |
+
[#336]: https://gsoc.trac.wordpress.org/ticket/336
|
2461 |
+
[#337]: https://gsoc.trac.wordpress.org/ticket/337
|
2462 |
+
[#343]: https://gsoc.trac.wordpress.org/ticket/343
|
2463 |
+
[#344]: https://gsoc.trac.wordpress.org/ticket/344
|
2464 |
+
|
2465 |
+
## 0.3
|
2466 |
+
- Add initial comment endpoints to get comments for a post, and get a single
|
2467 |
+
comment ([#320][])
|
2468 |
+
- Return a Post entity when updating a post, rather than wrapping it with
|
2469 |
+
useless text ([#329][])
|
2470 |
+
- Allow filtering the output as well as input. You can now use the
|
2471 |
+
`json_dispatch_args` filter for input as well as the `json_serve_request`
|
2472 |
+
filter for output to serve up alternative formats (e.g. MsgPack, XML (if
|
2473 |
+
you're insane))
|
2474 |
+
- Include a `profile` link in the index, to indicate the JSON Schema that the
|
2475 |
+
API conforms to. In the future, this will be versioned.
|
2476 |
+
|
2477 |
+
[#320]: https://gsoc.trac.wordpress.org/ticket/320
|
2478 |
+
[#329]: https://gsoc.trac.wordpress.org/ticket/329
|
2479 |
+
|
2480 |
+
## 0.2
|
2481 |
+
- Allow all public query vars to be passed to WP Query - Some private query vars
|
2482 |
+
can also be passed in, and all can if the user has `edit_posts`
|
2483 |
+
permissions ([#311][])
|
2484 |
+
- Pagination can now be handled by using the `page` argument without messing
|
2485 |
+
with WP Query syntax ([#266][])
|
2486 |
+
- The index now generates links for non-variable routes ([#268][])
|
2487 |
+
- Editing a post now supports the `If-Unmodified-Since` header. Pass this in to
|
2488 |
+
avoid conflicting edits ([#294][])
|
2489 |
+
- Post types and post statuses now have endpoints to access their data ([#268][])
|
2490 |
+
|
2491 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/0.1.2...0.2)
|
2492 |
+
|
2493 |
+
[#268]: https://gsoc.trac.wordpress.org/ticket/268
|
2494 |
+
[#294]: https://gsoc.trac.wordpress.org/ticket/294
|
2495 |
+
[#266]: https://gsoc.trac.wordpress.org/ticket/266
|
2496 |
+
[#311]: https://gsoc.trac.wordpress.org/ticket/311
|
2497 |
+
|
2498 |
+
## 0.1.2
|
2499 |
+
- Disable media handling to avoid fatal error ([#298][])
|
2500 |
+
|
2501 |
+
[#298]: http://gsoc.trac.wordpress.org/ticket/298
|
2502 |
+
|
2503 |
+
## 0.1.1
|
2504 |
+
- No changes, process error
|
2505 |
+
|
2506 |
+
## 0.1
|
2507 |
+
- Enable the code to be used via the plugin architecture (now uses rewrite rules
|
2508 |
+
if running in this mode)
|
2509 |
+
- Design documents are now functionally complete for the current codebase
|
2510 |
+
([#264][])
|
2511 |
+
- Add basic writing support ([#265][])
|
2512 |
+
- Filter fields by default - Unfiltered results are available via their
|
2513 |
+
corresponding `*_raw` key, which is only available to users with
|
2514 |
+
`edit_posts` ([#290][])
|
2515 |
+
- Use correct timezones for manual offsets (GMT+10, e.g.) ([#279][])
|
2516 |
+
- Allow permanently deleting posts ([#292])
|
2517 |
+
|
2518 |
+
[View all changes](https://github.com/rmccue/WP-API/compare/b3a8d7656ffc58c734aad95e0839609011b26781...0.1.1)
|
2519 |
+
|
2520 |
+
[#264]: https://gsoc.trac.wordpress.org/ticket/264
|
2521 |
+
[#265]: https://gsoc.trac.wordpress.org/ticket/265
|
2522 |
+
[#279]: https://gsoc.trac.wordpress.org/ticket/279
|
2523 |
+
[#290]: https://gsoc.trac.wordpress.org/ticket/290
|
2524 |
+
[#292]: https://gsoc.trac.wordpress.org/ticket/292
|
2525 |
+
|
2526 |
+
## 0.0.4
|
2527 |
+
- Hyperlinks now available in most constructs under the 'meta' key. At the
|
2528 |
+
moment, the only thing under this key is 'links', but more will come
|
2529 |
+
eventually. (Try browsing with a browser tool like JSONView; you should be
|
2530 |
+
able to view all content just by clicking the links.)
|
2531 |
+
- Accessing / now gives an index which briefly describes the API and gives
|
2532 |
+
links to more (also added the HIDDEN_ENDPOINT constant to hide from this).
|
2533 |
+
- Post collections now contain a summary of the post, with the full post
|
2534 |
+
available via the single post call. (prepare_post() has fields split into
|
2535 |
+
post and post-extended)
|
2536 |
+
- Post entities have dropped post_ prefixes, and custom_fields has changed to
|
2537 |
+
post_meta.
|
2538 |
+
- Now supports JSONP callback via the _jsonp argument. This can be disabled
|
2539 |
+
separately to the API itself, as it's only needed for
|
2540 |
+
cross-origin requests.
|
2541 |
+
- Internal: No longer extends the XMLRPC class. All relevant pieces have been
|
2542 |
+
copied over. Further work still needs to be done on this, but it's a start.
|
2543 |
+
|
2544 |
+
## 0.0.3:
|
2545 |
+
- Now accepts JSON bodies if an endpoint is marked with ACCEPT_JSON
|
CONTRIBUTING.md
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Contributing
|
2 |
+
Hi, and thanks for considering contributing! Before you do though, here's a few
|
3 |
+
notes on how best to contribute. Don't worry, I'll keep it short!
|
4 |
+
|
5 |
+
## Best Practices
|
6 |
+
|
7 |
+
### Commit Messages
|
8 |
+
Commit messages should follow the standard laid out in the git manual; that is,
|
9 |
+
a one-line summary ()
|
10 |
+
|
11 |
+
Short (50 chars or less) summary of changes
|
12 |
+
|
13 |
+
More detailed explanatory text, if necessary. Wrap it to about 72
|
14 |
+
characters or so. In some contexts, the first line is treated as the
|
15 |
+
subject of an email and the rest of the text as the body. The blank
|
16 |
+
line separating the summary from the body is critical (unless you omit
|
17 |
+
the body entirely); tools like rebase can get confused if you run the
|
18 |
+
two together.
|
19 |
+
|
20 |
+
Further paragraphs come after blank lines.
|
21 |
+
|
22 |
+
- Bullet points are okay, too
|
23 |
+
|
24 |
+
- Typically a hyphen or asterisk is used for the bullet, preceded by a
|
25 |
+
single space, with blank lines in between, but conventions vary here
|
26 |
+
|
27 |
+
## Commit Process
|
28 |
+
Changes are proposed in the form of pull requests by you, the contributor! After
|
29 |
+
submitting your proposed changes, a member of the API team will review your
|
30 |
+
commits and mark them for merge by assigning it to themselves. Your pull request
|
31 |
+
will then be merged after final review by another member.
|
README.md
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# WP REST API v2.0 (WP-API)
|
2 |
+
|
3 |
+
Access your WordPress site's data through an easy-to-use HTTP REST API.
|
4 |
+
|
5 |
+
[![Build Status](https://travis-ci.org/WP-API/WP-API.svg?branch=develop)](https://travis-ci.org/WP-API/WP-API)
|
6 |
+
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/WP-API/WP-API/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/WP-API/WP-API/?branch=develop)
|
7 |
+
|
8 |
+
## WARNING
|
9 |
+
|
10 |
+
The **"develop"** branch is undergoing substantial changes and is **NOT COMPLETE OR STABLE**. [Read the in-progress documentation](http://v2.wp-api.org/) to introduce yourself to endpoints, internal patterns, and implementation details.
|
11 |
+
|
12 |
+
The **"master"** branch represents a **BETA** of our next version release.
|
13 |
+
|
14 |
+
The latest **stable** version is available from the [WordPress Plugin Directory](https://wordpress.org/plugins/json-rest-api/).
|
15 |
+
|
16 |
+
## About
|
17 |
+
|
18 |
+
WordPress is moving towards becoming a fully-fledged application framework, and
|
19 |
+
we need new APIs. This project was born to create an easy-to-use,
|
20 |
+
easy-to-understand and well-tested framework for creating these APIs, plus
|
21 |
+
creating APIs for core.
|
22 |
+
|
23 |
+
This plugin provides an easy to use REST API, available via HTTP. Grab your
|
24 |
+
site's data in simple JSON format, including users, posts, taxonomies and more.
|
25 |
+
Retrieving or updating data is as simple as sending a HTTP request.
|
26 |
+
|
27 |
+
Want to get your site's posts? Simply send a `GET` request to `/wp-json/wp/v2/posts`.
|
28 |
+
Update user with ID 4? Send a `POST` request to `/wp-json/wp/v2/users/4`. Get all
|
29 |
+
posts with the search term "awesome"? `GET /wp-json/wp/v2/posts?s=awesome`.
|
30 |
+
It's that easy.
|
31 |
+
|
32 |
+
WP API exposes a simple yet easy interface to WP Query, the posts API, post meta
|
33 |
+
API, users API, revisions API and many more. Chances are, if you can do it with
|
34 |
+
WordPress, WP API will let you do it.
|
35 |
+
|
36 |
+
WP API also includes an easy-to-use JavaScript API based on Backbone models,
|
37 |
+
allowing plugin and theme developers to get up and running without needing to
|
38 |
+
know anything about the details of getting connected.
|
39 |
+
|
40 |
+
Check out [our documentation][docs] for information on what's available in the
|
41 |
+
API and how to use it. We've also got documentation on extending the API with
|
42 |
+
extra data for plugin and theme developers!
|
43 |
+
|
44 |
+
There's no fixed timeline for integration into core at this time, but getting closer!
|
45 |
+
|
46 |
+
|
47 |
+
## Installation
|
48 |
+
|
49 |
+
Drop this directory in and activate it. You need to be using pretty permalinks
|
50 |
+
to use the plugin, as it uses custom rewrite rules to power the API.
|
51 |
+
|
52 |
+
## Issue Tracking
|
53 |
+
|
54 |
+
All tickets for the project are being tracked on [GitHub][]. You can also take a
|
55 |
+
look at the [recent updates][] for the project.
|
56 |
+
|
57 |
+
## Security
|
58 |
+
|
59 |
+
We take the security of the API extremely seriously. If you think you've found
|
60 |
+
a security issue with the API (whether information disclosure, privilege
|
61 |
+
escalation, or another issue), we'd appreciate responsible disclosure as soon as
|
62 |
+
possible.
|
63 |
+
|
64 |
+
To report a security issue, you can either email `security[at]wordpress.org`, or
|
65 |
+
[file an issue on HackerOne][hackerone]. We will attempt to give an initial
|
66 |
+
response to security issues within 48 hours at most, however keep in mind that
|
67 |
+
the team is distributed across various timezones, and delays may occur as we
|
68 |
+
discuss internally.
|
69 |
+
|
70 |
+
(Please note: For testing, you should install a copy of the project and
|
71 |
+
WordPress on your own server. **Do not test on servers you do not own.**)
|
72 |
+
|
73 |
+
## License
|
74 |
+
|
75 |
+
[GPLv2+](http://www.gnu.org/licenses/gpl-2.0.html)
|
76 |
+
|
77 |
+
[docs]: http://v2.wp-api.org/
|
78 |
+
[GitHub]: https://github.com/WP-API/WP-API/issues
|
79 |
+
[recent updates]: https://make.wordpress.org/core/tag/json-api/
|
80 |
+
[hackerone]: https://hackerone.com/wp-api
|
compatibility-v1.php
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
add_filter( 'json_endpoints', 'json_v1_compatible_routes', 1000 );
|
4 |
+
add_filter( 'json_dispatch_request', 'json_v1_compatible_dispatch', 10, 3 );
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Make version 1 routes compatible with v2
|
8 |
+
*
|
9 |
+
* @param array $routes API routes
|
10 |
+
* @return array Filtered routes
|
11 |
+
*/
|
12 |
+
function json_v1_compatible_routes( $routes ) {
|
13 |
+
foreach ( $routes as $key => &$route ) {
|
14 |
+
// Single, with new-style registration
|
15 |
+
if ( isset( $route['callback'] ) || empty( $route ) ) {
|
16 |
+
continue;
|
17 |
+
}
|
18 |
+
|
19 |
+
// Multiple, with new-style registration
|
20 |
+
$first = reset( $route );
|
21 |
+
if ( isset( $first['callback'] ) ) {
|
22 |
+
continue;
|
23 |
+
}
|
24 |
+
|
25 |
+
// Old-style, map to new-style
|
26 |
+
if ( count( $route ) <= 2 && isset( $route[1] ) && ! is_array( $route[1] ) ) {
|
27 |
+
$route = array( $route );
|
28 |
+
}
|
29 |
+
|
30 |
+
foreach ( $route as &$handler ) {
|
31 |
+
$methods = isset( $handler[1] ) ? $handler[1] : WP_REST_Server::METHOD_GET;
|
32 |
+
|
33 |
+
$handler = array(
|
34 |
+
'callback' => $handler[0],
|
35 |
+
'methods' => $methods,
|
36 |
+
'v1_compat' => true,
|
37 |
+
);
|
38 |
+
}
|
39 |
+
}
|
40 |
+
|
41 |
+
return $routes;
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Use Reflection to match request parameters to function parameters
|
46 |
+
*
|
47 |
+
* @param mixed $result Result to use
|
48 |
+
* @param WP_JSON_Request $request Request object
|
49 |
+
* @return mixed
|
50 |
+
*/
|
51 |
+
function json_v1_compatible_dispatch( $result, $request ) {
|
52 |
+
// Allow other plugins to hijack too
|
53 |
+
if ( null !== $result ) {
|
54 |
+
return $result;
|
55 |
+
}
|
56 |
+
|
57 |
+
// Do we need the compatibility shim?
|
58 |
+
$params = $request->get_attributes();
|
59 |
+
if ( empty( $params['v1_compat'] ) ) {
|
60 |
+
return $result;
|
61 |
+
}
|
62 |
+
|
63 |
+
// Build up the arguments, old-style
|
64 |
+
$args = array_merge( $request->get_url_params(), $request->get_query_params() );
|
65 |
+
if ( $request->get_method() === 'POST' ) {
|
66 |
+
$args = array_merge( $args, $request->get_body_params() );
|
67 |
+
}
|
68 |
+
|
69 |
+
$args = json_v1_sort_callback_params( $params['callback'], $args );
|
70 |
+
if ( is_wp_error( $args ) ) {
|
71 |
+
return $args;
|
72 |
+
}
|
73 |
+
|
74 |
+
return call_user_func_array( $params['callback'], $args );
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Sort parameters by order specified in method declaration
|
79 |
+
*
|
80 |
+
* Takes a callback and a list of available params, then filters and sorts
|
81 |
+
* by the parameters the method actually needs, using the Reflection API
|
82 |
+
*
|
83 |
+
* @param callback $callback
|
84 |
+
* @param array $params
|
85 |
+
* @return array
|
86 |
+
*/
|
87 |
+
function json_v1_sort_callback_params( $callback, $provided ) {
|
88 |
+
if ( is_array( $callback ) ) {
|
89 |
+
$ref_func = new ReflectionMethod( $callback[0], $callback[1] );
|
90 |
+
} else {
|
91 |
+
$ref_func = new ReflectionFunction( $callback );
|
92 |
+
}
|
93 |
+
|
94 |
+
$wanted = $ref_func->getParameters();
|
95 |
+
$ordered_parameters = array();
|
96 |
+
|
97 |
+
foreach ( $wanted as $param ) {
|
98 |
+
if ( isset( $provided[ $param->getName() ] ) ) {
|
99 |
+
// We have this parameters in the list to choose from
|
100 |
+
$ordered_parameters[] = $provided[ $param->getName() ];
|
101 |
+
} elseif ( $param->isDefaultValueAvailable() ) {
|
102 |
+
// We don't have this parameter, but it's optional
|
103 |
+
$ordered_parameters[] = $param->getDefaultValue();
|
104 |
+
} else {
|
105 |
+
// We don't have this parameter and it wasn't optional, abort!
|
106 |
+
return new WP_Error( 'json_missing_callback_param', sprintf( __( 'Missing parameter %s' ), $param->getName() ), array( 'status' => 400 ) );
|
107 |
+
}
|
108 |
+
}
|
109 |
+
return $ordered_parameters;
|
110 |
+
}
|
docs/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
API Documentation
|
2 |
+
=================
|
3 |
+
Learn how the JSON REST API works from the ground up!
|
4 |
+
|
5 |
+
First time interacting with the API? Start with the [Getting Started][] guide,
|
6 |
+
which will introduce you to the basic concepts for working with the API.
|
7 |
+
|
8 |
+
From there, progress on to other [guides][] to learn in detail about parts of
|
9 |
+
the API.
|
10 |
+
|
11 |
+
Take a look at more detailed information on [post][post-routes] or
|
12 |
+
[media][media-routes], or read about [maximizing compatibility][compatibility]
|
13 |
+
with older clients.
|
14 |
+
|
15 |
+
Dive in deeper into the [schema details][schema] to better understand the little
|
16 |
+
details, or read about the [philosophy][] behind them. Read about the
|
17 |
+
[implementation details][implementation] on how the API works internally.
|
18 |
+
|
19 |
+
[Getting Started]: http://wp-api.org/guides/getting-started.html
|
20 |
+
[guides]: http://wp-api.org/guides.html
|
21 |
+
[post-routes]: http://wp-api.org/#posts
|
22 |
+
[media-routes]: http://wp-api.org/#media
|
23 |
+
[compatibility]: compatibility.md
|
24 |
+
[schema]: schema.md
|
25 |
+
[philosophy]: internals/philosophy.md
|
26 |
+
[implementation]: internals/implementation.md
|
docs/routes/routes.md
ADDED
@@ -0,0 +1,1569 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Posts
|
2 |
+
=====
|
3 |
+
|
4 |
+
Create a Post
|
5 |
+
-------------
|
6 |
+
|
7 |
+
POST /posts
|
8 |
+
|
9 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
10 |
+
|
11 |
+
### Input
|
12 |
+
The `data` parameter consists of the elements of the Post object to be
|
13 |
+
created. This data can be submitted via a regular HTTP multipart body, with
|
14 |
+
the Post keys and values set to the `data` parameter, or through a direct JSON
|
15 |
+
body.
|
16 |
+
|
17 |
+
That is, the following are equivalent:
|
18 |
+
|
19 |
+
```
|
20 |
+
Content-Type: application/x-www-form-urlencoded
|
21 |
+
|
22 |
+
data[title]=Hello%20World!&data[content_raw]=Content&data[excerpt_raw]=Excerpt
|
23 |
+
```
|
24 |
+
|
25 |
+
```
|
26 |
+
Content-Type: application/json
|
27 |
+
|
28 |
+
{"title":"Hello World!","content_raw":"Content","excerpt_raw":"Excerpt"}
|
29 |
+
```
|
30 |
+
|
31 |
+
The `data` parameter should be an object containing the following key value
|
32 |
+
pairs:
|
33 |
+
|
34 |
+
* `title` - Title of the post. (string) __*required*__
|
35 |
+
* `content_raw` - Full text of the post. (string) __*required*__
|
36 |
+
* `excerpt_raw` - Text for excerpt of the post. (string) *optional*
|
37 |
+
* `name` - Slug of the post. (string) *optional*
|
38 |
+
* `status` - Post status of the post: `draft`, `publish`, `pending`, `future`,
|
39 |
+
`private`, or any custom registered status. If providing a status of
|
40 |
+
`future`, you must specify a `date` in order for the post to be published as
|
41 |
+
expected. Default is `draft`. (string) *optional*
|
42 |
+
* `type` - Post type of the post: `post`, `page`, `link`, `nav_menu_item`, or
|
43 |
+
a any custom registered type. Default is `post`. (string) *optional*
|
44 |
+
* `date` - Date and time the post was, or should be, published in local time.
|
45 |
+
Date should be an RFC3339 timestamp](http://tools.ietf.org/html/rfc3339).
|
46 |
+
Example: 2014-01-01T12:20:52Z. Default is the local date and time. (string)
|
47 |
+
*optional*
|
48 |
+
* `date_gmt` - Date and time the post was, or should be, published in UTC time.
|
49 |
+
Date should be an [RFC3339 timestamp](http://tools.ietf.org/html/rfc3339).
|
50 |
+
Example: 201401-01T12:20:52Z. Default is the current GMT date and time.
|
51 |
+
(string) *optional*
|
52 |
+
* `author` - Author of the post. Author can be provided as a string of the
|
53 |
+
author's ID or as the User object of the author. Default is current user.
|
54 |
+
(object \| string) *optional*
|
55 |
+
* `password` - Password for protecting the post. Default is empty string.
|
56 |
+
(string) *optional*
|
57 |
+
* `post_parent` - Post ID of the post parent. Default is 0. (integer)
|
58 |
+
*optional*
|
59 |
+
* `post_format` - Format of the post. Default is `standard`. (string)
|
60 |
+
*optional*
|
61 |
+
* `menu_order` - The order in which posts specified as the `page` type should
|
62 |
+
appear in supported menus. Default 0. (integer) *optional*
|
63 |
+
* `comment_status` - Comment status for the post: `open` or `closed`.
|
64 |
+
Indicates whether users can submit comments to the post. Default is the
|
65 |
+
option 'default_comment_status', or 'closed'. (string) *optional*
|
66 |
+
* `ping_status` - Ping status for the post: `open` or `closed`. Indicates
|
67 |
+
whether users can submit pingbacks or trackbacks to the post. Default is the
|
68 |
+
option 'default_ping_status'. (string) *optional*
|
69 |
+
* `sticky` - Sticky status for the post: `true` or `false`. Default is
|
70 |
+
`false`. (boolean) *optional*
|
71 |
+
* `post_meta` - Post meta entries of the post. Post meta should be an array
|
72 |
+
of one or more Meta objects for each post meta entry. See the Create Meta
|
73 |
+
for a Post endpoint for the key value pairs. (array) *optional*
|
74 |
+
|
75 |
+
|
76 |
+
### Response
|
77 |
+
On a successful creation, a 201 Created status is given, indicating that the
|
78 |
+
post has been created. The post is available canonically from the URL specified
|
79 |
+
in the Location header.
|
80 |
+
|
81 |
+
The new Post entity is also returned in the body for convienience.
|
82 |
+
|
83 |
+
If the client is not authenticated, a 403 Forbidden response is given.
|
84 |
+
|
85 |
+
Retrieve Posts
|
86 |
+
--------------
|
87 |
+
The Posts endpoint returns a Post Collection containing a subset of the site's
|
88 |
+
posts.
|
89 |
+
|
90 |
+
GET /posts
|
91 |
+
|
92 |
+
### Input
|
93 |
+
#### `filter`
|
94 |
+
The `filter` parameter controls the parameters used to query for posts.
|
95 |
+
|
96 |
+
**Note:** Only "public" query variables are available via the API, as not all
|
97 |
+
query variables are safe to expose. "Private" query variables are also available
|
98 |
+
when authenticated as a user with `edit_posts`. Other query variables can be
|
99 |
+
registered via the `query_vars` filter, or `json_query_vars` for API-specific
|
100 |
+
query variables.
|
101 |
+
|
102 |
+
Extended documentation on the query variables is available from
|
103 |
+
[the codex](http://codex.wordpress.org/Class_Reference/WP_Query).
|
104 |
+
|
105 |
+
The following query variables are available to the API:
|
106 |
+
|
107 |
+
* `m`
|
108 |
+
* `p`
|
109 |
+
* `posts`
|
110 |
+
* `w`
|
111 |
+
* `cat`
|
112 |
+
* `withcomments`
|
113 |
+
* `withoutcomments`
|
114 |
+
* `s`
|
115 |
+
* `search`
|
116 |
+
* `exact`
|
117 |
+
* `sentence`
|
118 |
+
* `calendar`
|
119 |
+
* `page`
|
120 |
+
* `paged`
|
121 |
+
* `more`
|
122 |
+
* `tb`
|
123 |
+
* `pb`
|
124 |
+
* `author`
|
125 |
+
* `order`
|
126 |
+
* `orderby`
|
127 |
+
* `year`
|
128 |
+
* `monthnum`
|
129 |
+
* `day`
|
130 |
+
* `hour`
|
131 |
+
* `minute`
|
132 |
+
* `second`
|
133 |
+
* `name`
|
134 |
+
* `category_name`
|
135 |
+
* `tag`
|
136 |
+
* `feed`
|
137 |
+
* `author_name`
|
138 |
+
* `static`
|
139 |
+
* `pagename`
|
140 |
+
* `page_id`
|
141 |
+
* `error`
|
142 |
+
* `comments_popup`
|
143 |
+
* `attachment`
|
144 |
+
* `attachment_id`
|
145 |
+
* `subpost`
|
146 |
+
* `subpost_id`
|
147 |
+
* `preview`
|
148 |
+
* `robots`
|
149 |
+
* `taxonomy`
|
150 |
+
* `term`
|
151 |
+
* `cpage`
|
152 |
+
* `posts_per_page`
|
153 |
+
|
154 |
+
In addition, the following are available when authenticated as a user with
|
155 |
+
`edit_posts`:
|
156 |
+
|
157 |
+
* `offset`
|
158 |
+
* `posts_per_archive_page`
|
159 |
+
* `showposts`
|
160 |
+
* `nopaging`
|
161 |
+
* `post_type`
|
162 |
+
* `post_status`
|
163 |
+
* `category__in`
|
164 |
+
* `category__not_in`
|
165 |
+
* `category__and`
|
166 |
+
* `tag__in`
|
167 |
+
* `tag__not_in`
|
168 |
+
* `tag__and`
|
169 |
+
* `tag_slug__in`
|
170 |
+
* `tag_slug__and`
|
171 |
+
* `tag_id`
|
172 |
+
* `post_mime_type`
|
173 |
+
* `perm`
|
174 |
+
* `comments_per_page`
|
175 |
+
* `post__in`
|
176 |
+
* `post__not_in`
|
177 |
+
* `post_parent`
|
178 |
+
* `post_parent__in`
|
179 |
+
* `post_parent__not_in`
|
180 |
+
|
181 |
+
```
|
182 |
+
GET /posts?filter[posts_per_page]=8&filter[order]=ASC
|
183 |
+
```
|
184 |
+
|
185 |
+
#### `context`
|
186 |
+
The `context` parameter controls the format of the data to return. See the
|
187 |
+
Retrieve a Post endpoint for available contexts.
|
188 |
+
|
189 |
+
Default is "view". (string)
|
190 |
+
|
191 |
+
|
192 |
+
#### `type`
|
193 |
+
The `type` parameter specifies the post type to retrieve. This can either be a
|
194 |
+
string or an array of types.
|
195 |
+
|
196 |
+
Note that arrays are specified using the `[]` URL syntax. e.g.
|
197 |
+
|
198 |
+
```
|
199 |
+
GET /posts?type[]=post&type[]=page
|
200 |
+
```
|
201 |
+
|
202 |
+
Default is "post". (string)
|
203 |
+
|
204 |
+
|
205 |
+
### Response
|
206 |
+
The response is a Post Collection document containing the requested Posts if
|
207 |
+
available.
|
208 |
+
|
209 |
+
|
210 |
+
Retrieve a Post
|
211 |
+
---------------
|
212 |
+
|
213 |
+
GET /posts/<id>
|
214 |
+
|
215 |
+
### Input
|
216 |
+
#### `context`
|
217 |
+
The `context` parameter controls the format of the data to return. The
|
218 |
+
following contexts are available:
|
219 |
+
|
220 |
+
* `view`: The default context. Gives the normal User entity.
|
221 |
+
* `edit`: Context used for extra fields relevant to updating a user. Includes
|
222 |
+
the `title_raw`, `content_raw`, `guid_raw` and `post_meta` fields, suitable
|
223 |
+
for editing the post.
|
224 |
+
* `parent`: Context used when embedding the response inside another (e.g. post
|
225 |
+
author). This is intended as a minimal subset of the user data to reduce
|
226 |
+
response size. Returns the `parent` field as an ID, rather than an embedded
|
227 |
+
post, to ensure we don't traverse the entire post hierarchy.
|
228 |
+
|
229 |
+
### Response
|
230 |
+
The response is a Post entity containing the requested Post if available. The
|
231 |
+
fields available on the Post depend on the `context` parameter.
|
232 |
+
|
233 |
+
|
234 |
+
Edit a Post
|
235 |
+
-----------
|
236 |
+
|
237 |
+
PUT /posts/<id>
|
238 |
+
|
239 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
240 |
+
|
241 |
+
For compatibility reasons, this endpoint also accepts the POST and PATCH
|
242 |
+
methods. Both of these methods have the same behaviour as using PUT. It is
|
243 |
+
recommended to use PUT if available to fit with REST convention.
|
244 |
+
|
245 |
+
### Input
|
246 |
+
The `data` parameter consists of Post ID and the elements of the Post object
|
247 |
+
to be modified. This data can be submitted via a regular HTTP multipart body,
|
248 |
+
with the Post keys and values set to the `data` parameter, or through a direct
|
249 |
+
JSON body. See the Create Post endpoint for an example.
|
250 |
+
|
251 |
+
The `data` parameter should be an object containing the following key value
|
252 |
+
pairs:
|
253 |
+
|
254 |
+
* `ID` - Unique ID of the post. (integer) __*required*__
|
255 |
+
* `title` - Title of the post. (string) __*required*__
|
256 |
+
* `content_raw` - Full text of the post. (string) __*required*__
|
257 |
+
* `excerpt_raw` - Text for excerpt of the post. (string) *optional*
|
258 |
+
* `name` - Slug of the post. (string) *optional*
|
259 |
+
* `status` - Post status of the post: `draft`, `publish`, `pending`, `future`,
|
260 |
+
`private`, or any custom registered status. If providing a status of
|
261 |
+
`future`, you must specify a `date` in order for the post to be published as
|
262 |
+
expected. Default is `draft`. (string) *optional*
|
263 |
+
* `type` - Post type of the post: `post`, `page`, `link`, `nav_menu_item`, or
|
264 |
+
a any custom registered type. Default is `post`. (string) *optional*
|
265 |
+
* `date` - Date and time the post was, or should be, published in local time.
|
266 |
+
Date should be an RFC3339 timestamp](http://tools.ietf.org/html/rfc3339).
|
267 |
+
Example: 2014-01-01T12:20:52Z. Default is the local date and time. (string)
|
268 |
+
*optional*
|
269 |
+
* `date_gmt` - Date and time the post was, or should be, published in UTC time.
|
270 |
+
Date should be an [RFC3339 timestamp](http://tools.ietf.org/html/rfc3339).
|
271 |
+
Example: 201401-01T12:20:52Z. Default is the current GMT date and time.
|
272 |
+
(string) *optional*
|
273 |
+
* `author` - Author of the post. Author can be provided as a string of the
|
274 |
+
author's ID or as the User object of the author. Default is current user.
|
275 |
+
(object \| string) *optional*
|
276 |
+
* `password` - Password for protecting the post. Default is empty string.
|
277 |
+
(string) *optional*
|
278 |
+
* `post_parent` - Post ID of the post parent. Default is 0. (integer)
|
279 |
+
*optional*
|
280 |
+
* `post_format` - Format of the post. Default is `standard`. (string)
|
281 |
+
*optional*
|
282 |
+
* `menu_order` - The order in which posts specified as the `page` type should
|
283 |
+
appear in supported menus. Default 0. (integer) *optional*
|
284 |
+
* `comment_status` - Comment status for the post: `open` or `closed`.
|
285 |
+
Indicates whether users can submit comments to the post. Default is the
|
286 |
+
option 'default_comment_status', or 'closed'. (string) *optional*
|
287 |
+
* `ping_status` - Ping status for the post: `open` or `closed`. Indicates
|
288 |
+
whether users can submit pingbacks or trackbacks to the post. Default is the
|
289 |
+
option 'default_ping_status'. (string) *optional*
|
290 |
+
* `sticky` - Sticky status for the post: `true` or `false`. Default is
|
291 |
+
`false`. (boolean) *optional*
|
292 |
+
* `post_meta` - Post meta entries of the post. Post meta should be an array
|
293 |
+
of one or more Meta objects for each post meta entry. See the Edit Meta
|
294 |
+
for a Post endpoint for the key value pairs. (array) *optional*
|
295 |
+
|
296 |
+
|
297 |
+
### Response
|
298 |
+
On a successful update, a 200 OK status is given, indicating the post has been
|
299 |
+
updated. The updated Post entity is returned in the body.
|
300 |
+
|
301 |
+
If the client is not authenticated, a 403 Forbidden response is sent.
|
302 |
+
|
303 |
+
Delete a Post
|
304 |
+
-------------
|
305 |
+
|
306 |
+
DELETE /posts/<id>
|
307 |
+
|
308 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
309 |
+
|
310 |
+
### Input
|
311 |
+
#### `force`
|
312 |
+
The `force` parameter controls whether the post is permanently deleted or not.
|
313 |
+
By default, this is set to false, indicating that the post will be sent to an
|
314 |
+
intermediate storage (such as the trash) allowing it to be restored later. If
|
315 |
+
set to true, the post will not be able to be restored by the user.
|
316 |
+
|
317 |
+
Default is false. (boolean)
|
318 |
+
|
319 |
+
### Response
|
320 |
+
On successful deletion, a 202 Accepted status code will be returned, indicating
|
321 |
+
that the post has been moved to the trash for permanent deletion at a
|
322 |
+
later date.
|
323 |
+
|
324 |
+
If force was set to true, a 200 OK status code will be returned instead,
|
325 |
+
indicating that the post has been permanently deleted.
|
326 |
+
|
327 |
+
If the client is not authenticated, a 403 Forbidden status code will be returned.
|
328 |
+
|
329 |
+
Retrieve Revisions for a Post
|
330 |
+
------------------------
|
331 |
+
|
332 |
+
GET /posts/<id>/revisions
|
333 |
+
|
334 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
335 |
+
|
336 |
+
### Response
|
337 |
+
If successful, returns a 200 OK status code and revisions for the given post.
|
338 |
+
|
339 |
+
If the client is not authenticated, a 403 Forbidden status code will be returned.
|
340 |
+
|
341 |
+
|
342 |
+
Create Meta for a Post
|
343 |
+
------------------------
|
344 |
+
|
345 |
+
POST /posts/<id>/meta
|
346 |
+
|
347 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
348 |
+
|
349 |
+
Note that the access rules for metadata apply here (see [Retrieve Meta for
|
350 |
+
a Post](http://wp-api.org/#posts_retrieve-meta-for-a-post) ). Any submitted data that violates an access rule (e.g. sending
|
351 |
+
serialized data) will result in a 403 error.
|
352 |
+
|
353 |
+
### Input
|
354 |
+
The supplied data should be a Meta object. This data can be submitted via a
|
355 |
+
regular HTTP multipart body, with the Meta key and value set with the `data`
|
356 |
+
parameter, or through a direct JSON body.
|
357 |
+
|
358 |
+
The `data` parameter should be an object containing the following key value
|
359 |
+
pairs:
|
360 |
+
|
361 |
+
* `key` - The post meta key to be created. (string) *required*
|
362 |
+
* `value` - The post meta value for the key provided. (string) *required*
|
363 |
+
|
364 |
+
### Response
|
365 |
+
On a successful creation, a 201 Created status is given, indicating that the
|
366 |
+
Meta has been created. The post meta is available canonically from the URL
|
367 |
+
specified in the Location header.
|
368 |
+
|
369 |
+
The new Meta entity is also returned in the body for convienience.
|
370 |
+
|
371 |
+
If the client is not authenticated, a 403 Forbidden status code will be returned.
|
372 |
+
|
373 |
+
Retrieve Meta for a Post
|
374 |
+
------------------------
|
375 |
+
|
376 |
+
GET /posts/<id>/meta
|
377 |
+
|
378 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
379 |
+
|
380 |
+
WordPress metadata follows some special rules for access:
|
381 |
+
|
382 |
+
* Metadata is only available to authenticated clients, as the fields are "raw"
|
383 |
+
values from the database. The API cannot ensure that it's not leaking private
|
384 |
+
data, although we're working on changing WordPress to support this.
|
385 |
+
|
386 |
+
* "Complex" metadata is not available from the API. Only simple values, such as
|
387 |
+
numbers, strings, and booleans, are available via the meta endpoints. Complex
|
388 |
+
values, such as arrays and objects do not have a lossless (one-to-one)
|
389 |
+
representation in JSON. Exposing the serialized value could leak internal
|
390 |
+
implementation details and pose a security risk.
|
391 |
+
|
392 |
+
* "Protected" metadata is not available from the API. This includes any metadata
|
393 |
+
with a key prefixed with `_`, as well as any meta marked as protected by
|
394 |
+
plugins. Protected meta is used to store internal data by many plugins and
|
395 |
+
cannot be exposed to external clients.
|
396 |
+
|
397 |
+
### Response
|
398 |
+
The response is a Meta entity containing all the post_meta for the specified
|
399 |
+
Post if available.
|
400 |
+
|
401 |
+
Returns a 403 Forbidden status code if the client is not authenticated.
|
402 |
+
|
403 |
+
Retrieve a Meta for a Post
|
404 |
+
------------------------
|
405 |
+
|
406 |
+
GET /posts/<id>/meta/<mid>
|
407 |
+
|
408 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
409 |
+
|
410 |
+
Note that the access rules for metadata apply here (see [Retrieve Meta for
|
411 |
+
a Post](http://wp-api.org/#posts_retrieve-meta-for-a-post) ).
|
412 |
+
|
413 |
+
### Response
|
414 |
+
The response is a Meta entity containing the post_meta for the specified Meta and
|
415 |
+
Post if available.
|
416 |
+
|
417 |
+
Returns a 403 Forbidden status code if the client is not authenticated.
|
418 |
+
|
419 |
+
Edit a Meta for a Post
|
420 |
+
------------------------
|
421 |
+
|
422 |
+
PUT /posts/<id>/meta/<mid>
|
423 |
+
|
424 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
425 |
+
|
426 |
+
Note that the access rules for metadata apply here (see [Retrieve Meta for
|
427 |
+
a Post](http://wp-api.org/#posts_retrieve-meta-for-a-post) ). Any submitted data that violates an access rule (e.g. sending
|
428 |
+
serialized data) will result in a 403 error.
|
429 |
+
|
430 |
+
### Input
|
431 |
+
The supplied data should be a Meta object. This data can be submitted via a
|
432 |
+
regular HTTP multipart body, with the Meta key and value set with the `data`
|
433 |
+
parameter, or through a direct JSON body.
|
434 |
+
|
435 |
+
The `data` parameter should be an array containing the following key value pairs:
|
436 |
+
|
437 |
+
* `key` - The post meta key to be updated. (string) *required*
|
438 |
+
* `value` - The post meta value for the key provided. (string) *required*
|
439 |
+
|
440 |
+
### Response
|
441 |
+
On a successful update, a 200 OK status is given, indicating the post_meta has
|
442 |
+
been updated. The updated Meta entity is returned in the body.
|
443 |
+
|
444 |
+
If the client is not authenticated, a 403 Forbidden status code is returned.
|
445 |
+
|
446 |
+
Delete a Meta for a Post
|
447 |
+
-------------
|
448 |
+
|
449 |
+
DELETE /posts/<id>/meta/<mid>
|
450 |
+
|
451 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
452 |
+
|
453 |
+
Note that the access rules for metadata apply here (see Retrieve Meta for
|
454 |
+
a Post). Attempting to delete data that violates an access rule (e.g. sending
|
455 |
+
serialized data) will result in a 403 error.
|
456 |
+
|
457 |
+
### Response
|
458 |
+
On successful deletion, a 200 OK status code will be returned, indicating
|
459 |
+
that the post_meta has been permanently deleted.
|
460 |
+
|
461 |
+
If the client is not authenticated, a 403 Forbidden status code is returned.
|
462 |
+
|
463 |
+
Media
|
464 |
+
=====
|
465 |
+
|
466 |
+
|
467 |
+
Create an Attachment
|
468 |
+
--------------------
|
469 |
+
The Create Attachment endpoint is used to create the raw data for an attachment.
|
470 |
+
This is a binary object (blob), such as image data or a video.
|
471 |
+
|
472 |
+
POST /media
|
473 |
+
|
474 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
475 |
+
|
476 |
+
### Input
|
477 |
+
The attachment creation endpoint can accept data in two forms.
|
478 |
+
|
479 |
+
The primary input method accepts raw data POSTed with the corresponding content
|
480 |
+
type set via the `Content-Type` HTTP header. This is the preferred submission
|
481 |
+
method.
|
482 |
+
|
483 |
+
The secondary input method accepts data POSTed via `multipart/form-data`, as per
|
484 |
+
[RFC 2388][]. The uploaded file should be submitted with the name field set to
|
485 |
+
"file", and the filename field set to the relevant filename for the file.
|
486 |
+
|
487 |
+
In addition, a `Content-MD5` header can be set with the MD5 hash of the file, to
|
488 |
+
enable the server to check for consistency errors. If the supplied hash does not
|
489 |
+
match the hash calculated on the server, a 412 Precondition Failed header will
|
490 |
+
be issued.
|
491 |
+
|
492 |
+
[RFC 2388]: http://tools.ietf.org/html/rfc2388
|
493 |
+
|
494 |
+
### Response
|
495 |
+
On a successful creation, a 201 Created status is given, indicating that the
|
496 |
+
attachment has been created. The attachment is available canonically from the
|
497 |
+
URL specified in the Location header.
|
498 |
+
|
499 |
+
The new Attachment entity is also returned in the body for convienience.
|
500 |
+
|
501 |
+
Returns a 403 Forbidden status code if the client is not authenticated.
|
502 |
+
|
503 |
+
Get Attachments
|
504 |
+
---------------
|
505 |
+
The Attachments endpoint returns an Attachment collection containing a subset of
|
506 |
+
the site's attachments.
|
507 |
+
|
508 |
+
This endpoint is an extended version of the Post retrieval endpoint.
|
509 |
+
|
510 |
+
GET /media
|
511 |
+
|
512 |
+
### Input
|
513 |
+
#### `fields`
|
514 |
+
...
|
515 |
+
|
516 |
+
### Response
|
517 |
+
The response is an Attachment entity containing the requested Attachment if
|
518 |
+
available.
|
519 |
+
|
520 |
+
|
521 |
+
Users
|
522 |
+
=====
|
523 |
+
|
524 |
+
|
525 |
+
Create a User
|
526 |
+
-------------
|
527 |
+
|
528 |
+
POST /users
|
529 |
+
|
530 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
531 |
+
|
532 |
+
### Input
|
533 |
+
The supplied data should be a User object. This data can be submitted via a
|
534 |
+
regular HTTP multipart body, with User values set as values to the `data`
|
535 |
+
parameter, or through a direct JSON body.
|
536 |
+
|
537 |
+
That is, the following are equivalent:
|
538 |
+
|
539 |
+
Content-Type: application/x-www-form-urlencoded
|
540 |
+
|
541 |
+
data[username]=newuser&data[name]=New%20User&data[password]=secret
|
542 |
+
|
543 |
+
|
544 |
+
Content-Type: application/json
|
545 |
+
|
546 |
+
{"username":"newuser","name":"New User","password":"secret"}
|
547 |
+
|
548 |
+
### Response
|
549 |
+
On a successful creation, a 201 Created status is given, indicating that the
|
550 |
+
user has been created. The user is available canonically from the URL specified
|
551 |
+
in the Location header.
|
552 |
+
|
553 |
+
The new User entity is also returned in the body for convenience.
|
554 |
+
|
555 |
+
A 403 Forbidden status is returned if the client is not authenticated.
|
556 |
+
|
557 |
+
Retrieve Users
|
558 |
+
--------------
|
559 |
+
The Users endpoint returns a User Collection containing a subset of the site's
|
560 |
+
users.
|
561 |
+
|
562 |
+
GET /users
|
563 |
+
|
564 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
565 |
+
|
566 |
+
|
567 |
+
### Input
|
568 |
+
#### `filter`
|
569 |
+
The `filter` parameter controls the query parameters. It is essentially a subset
|
570 |
+
of the parameters available to [`WP_User_Query`](http://codex.wordpress.org/Class_Reference/WP_User_Query).
|
571 |
+
|
572 |
+
The parameter should be an array of the following key/value pairs:
|
573 |
+
|
574 |
+
* `number` - Number of users to retrieve, use `-1` for all users. Default
|
575 |
+
is set by the site. (integer)
|
576 |
+
* `offset` - Number of users to skip. Default is 0. (integer)
|
577 |
+
* `orderby` - Parameter to search by, as per [`WP_User_Query`](https://codex.wordpress.org/Class_Reference/WP_User_Query#Order_.26_Orderby_Parameters).
|
578 |
+
Default is "user_login". (string)
|
579 |
+
* `order` - Order to sort by. Default is "ASC". (string, "ASC" or "DESC")
|
580 |
+
* `s` - Keyword to search for. (string)
|
581 |
+
|
582 |
+
### Response
|
583 |
+
The response is a User Collection document containing the requested Users if
|
584 |
+
available.
|
585 |
+
|
586 |
+
A 403 Forbidden status is returned if the client is not authenticated.
|
587 |
+
|
588 |
+
|
589 |
+
Retrieve a User
|
590 |
+
---------------
|
591 |
+
|
592 |
+
GET /users/<id>
|
593 |
+
|
594 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
595 |
+
|
596 |
+
### Input
|
597 |
+
#### `context`
|
598 |
+
The `context` parameter controls the format of the data to return. The following
|
599 |
+
contexts are available:
|
600 |
+
|
601 |
+
* `view`: The default context. Gives the normal User entity.
|
602 |
+
* `edit`: Context used for extra fields relevant to updating a user. Includes
|
603 |
+
the `extra_capabilities` field; this field contains the capabilities assigned
|
604 |
+
to the user themselves, rather than those inherited from their roles. Requires [authentication](http://wp-api.org/guides/authentication.html).
|
605 |
+
* `embed`: Context used when embedding the response inside another (e.g. post
|
606 |
+
author). This is intended as a minimal subset of the user data to reduce
|
607 |
+
response size. Excludes `roles` and `capabilities`.
|
608 |
+
|
609 |
+
Default is "view". (string)
|
610 |
+
|
611 |
+
### Response
|
612 |
+
The response is a User entity containing the requested User if available. The
|
613 |
+
fields available on the User depend on the `context` parameter.
|
614 |
+
|
615 |
+
A 403 Forbidden status is returned if the client is not authenticated.
|
616 |
+
|
617 |
+
|
618 |
+
Retrieve Current User
|
619 |
+
-------------
|
620 |
+
|
621 |
+
GET /users/me
|
622 |
+
|
623 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
624 |
+
|
625 |
+
This endpoint offers a permalink to get the current user, without needing to
|
626 |
+
know the user's ID.
|
627 |
+
|
628 |
+
### Input
|
629 |
+
#### `context`
|
630 |
+
The `context` parameter controls the format of the data to return. See the
|
631 |
+
Retrieve a User endpoint for available contexts.
|
632 |
+
|
633 |
+
Default is "view". (string)
|
634 |
+
|
635 |
+
### Response
|
636 |
+
If the client is currently logged in, a 302 Found status is given. The User is
|
637 |
+
available canonically from the URL specified in the Location header.
|
638 |
+
|
639 |
+
The User entity containing the current User is also returned in the body for
|
640 |
+
convenience. The fields available on the User depend on the `context` parameter.
|
641 |
+
|
642 |
+
If the client is not logged in, a 403 Forbidden status is given.
|
643 |
+
|
644 |
+
|
645 |
+
Edit a User
|
646 |
+
-----------
|
647 |
+
|
648 |
+
PUT /users/<id>
|
649 |
+
|
650 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
651 |
+
|
652 |
+
For compatibility reasons, this endpoint also accepts the POST and PATCH
|
653 |
+
methods. Both of these methods have the same behaviour as using PUT. It is
|
654 |
+
recommended to use PUT if available to fit with REST convention.
|
655 |
+
|
656 |
+
### Input
|
657 |
+
The supplied data should be a User object. This data can be submitted via a
|
658 |
+
regular HTTP multipart body, with User values set as values to the `data`
|
659 |
+
parameter, or through a direct JSON body. See the Create User endpoint for an
|
660 |
+
example.
|
661 |
+
|
662 |
+
### Response
|
663 |
+
On a successful update, a 200 OK status is given, indicating the user has been
|
664 |
+
updated. The updated User entity is returned in the body.
|
665 |
+
|
666 |
+
If the client is not logged in, a 403 Forbidden status is given.
|
667 |
+
|
668 |
+
Delete a User
|
669 |
+
-------------
|
670 |
+
|
671 |
+
DELETE /users/<id>
|
672 |
+
|
673 |
+
Requires [authentication](http://wp-api.org/guides/authentication.html)
|
674 |
+
|
675 |
+
### Input
|
676 |
+
#### `force`
|
677 |
+
The `force` parameter controls whether the user is permanently deleted or not.
|
678 |
+
By default, this is set to false, indicating that the user will be sent to an
|
679 |
+
intermediate storage (such as the trash) allowing it to be restored later. If
|
680 |
+
set to true, the user will not be able to be restored.
|
681 |
+
|
682 |
+
Default is false. (boolean)
|
683 |
+
|
684 |
+
#### `reassign`
|
685 |
+
The `reassign` parameter controls whether the deleted user's content is
|
686 |
+
reassigned to a new User or not. If set to `null`, the deleted user's content
|
687 |
+
will not be reassigned.
|
688 |
+
|
689 |
+
Default is null. (integer)
|
690 |
+
|
691 |
+
|
692 |
+
### Response
|
693 |
+
On successful deletion, a 202 Accepted status code will be returned, indicating
|
694 |
+
that the user has been moved to the trash for permanent deletion at a
|
695 |
+
later date.
|
696 |
+
|
697 |
+
If force was set to true, a 200 OK status code will be returned instead,
|
698 |
+
indicating that the user has been permanently deleted.
|
699 |
+
|
700 |
+
If the client is not authenticated, a 403 Forbidden status is given.
|
701 |
+
|
702 |
+
Taxonomies
|
703 |
+
==========
|
704 |
+
|
705 |
+
|
706 |
+
Retrieve All Taxonomies
|
707 |
+
-----------------------
|
708 |
+
The Taxonomies endpoint returns a collection containing objects for each of the
|
709 |
+
site's registered taxonomies.
|
710 |
+
|
711 |
+
GET /taxonomies
|
712 |
+
|
713 |
+
|
714 |
+
### Response
|
715 |
+
The response is a collection document containing all registered taxonomies.
|
716 |
+
|
717 |
+
|
718 |
+
Retrieve a Taxonomy
|
719 |
+
-------------------
|
720 |
+
|
721 |
+
GET /taxonomies/<taxonomy>
|
722 |
+
|
723 |
+
### Response
|
724 |
+
The response is a Taxonomy entity containing the requested Taxonomy, if available.
|
725 |
+
|
726 |
+
|
727 |
+
Retrieve Terms for a Taxonomy
|
728 |
+
-----------------------------
|
729 |
+
|
730 |
+
GET /taxonomies/<taxonomy>/terms
|
731 |
+
|
732 |
+
### Response
|
733 |
+
The response is a collection of taxonomy terms for the specified Taxonomy, if
|
734 |
+
available.
|
735 |
+
|
736 |
+
Retrieve a Taxonomy Term
|
737 |
+
------------------------
|
738 |
+
|
739 |
+
GET /taxonomies/<taxonomy>/terms/<id>
|
740 |
+
|
741 |
+
### Response
|
742 |
+
The response is a Taxonomy entity object containing the Taxonomy with the
|
743 |
+
requested ID, if available.
|
744 |
+
|
745 |
+
SCHEMA
|
746 |
+
============
|
747 |
+
The API is designed around two types of responses: entities, and collections.
|
748 |
+
Entities are JSON objects representing internal objects, both abstract and
|
749 |
+
WordPress objects. Collections are JSON arrays of Entities.
|
750 |
+
|
751 |
+
This document is for clients and providers wanting to ensure full compliance
|
752 |
+
with the specification.
|
753 |
+
|
754 |
+
|
755 |
+
Definitions
|
756 |
+
==========
|
757 |
+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
758 |
+
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
759 |
+
interpreted as described in [RFC2119][].
|
760 |
+
|
761 |
+
* Provider: A site making the API available for use
|
762 |
+
* Consumer: An application accessing and interacting with the API
|
763 |
+
* slug: A URL-friendly human-readable identifier, usually derived from the title
|
764 |
+
of the entity.
|
765 |
+
|
766 |
+
[RFC2119]: http://tools.ietf.org/html/rfc2119
|
767 |
+
|
768 |
+
|
769 |
+
### ABNF
|
770 |
+
Augmented Backus-Naur Form (ABNF) is to be interpreted as described in
|
771 |
+
[RFC5234][]. In addition, the following basic rules are used to describe basic
|
772 |
+
parsing constructs above the standard JSON parsing rules.
|
773 |
+
|
774 |
+
token = 1*<any OCTET except CTLs> ; DQUOTE must be escaped with "\"
|
775 |
+
|
776 |
+
Note that as per ABNF, literal strings are case insensitive. That is:
|
777 |
+
|
778 |
+
example-field = "id"
|
779 |
+
example-field = "ID"
|
780 |
+
|
781 |
+
Providers SHOULD use the capitalisation as per this specification to ensure
|
782 |
+
maximum compatibility with consumers. Consumers SHOULD ignore the case of
|
783 |
+
literal strings when parsing data.
|
784 |
+
|
785 |
+
[RFC5234]: http://tools.ietf.org/html/rfc5234
|
786 |
+
|
787 |
+
|
788 |
+
Entities
|
789 |
+
========
|
790 |
+
|
791 |
+
Index
|
792 |
+
-----
|
793 |
+
The Index entity is a JSON object with site properties. The following properties
|
794 |
+
are defined for the Index entity object.
|
795 |
+
|
796 |
+
### `name`
|
797 |
+
The `name` field is a string with the site's name.
|
798 |
+
|
799 |
+
### `description`
|
800 |
+
The `description` field is a string with the site's description.
|
801 |
+
|
802 |
+
### `URL`
|
803 |
+
The `URL` field is a string with the URL to the site itself.
|
804 |
+
|
805 |
+
### `routes`
|
806 |
+
The `routes` field is an object with keys as a route and the values as a route
|
807 |
+
descriptor.
|
808 |
+
|
809 |
+
The route is a string giving the URL template for the route, relative to the API
|
810 |
+
root. The template contains URL parts separated by forward slashes, with each
|
811 |
+
URL part either a static string, or a route variable encased in angle brackets.
|
812 |
+
|
813 |
+
route = ( "/"
|
814 |
+
/ *( "/" ( token / route-variable ) ) )
|
815 |
+
route-variable = "<" token ">"
|
816 |
+
|
817 |
+
These routes can be converted into URLs by replacing all route variables with
|
818 |
+
their relevant values, then concatenating the relative URL to the API base.
|
819 |
+
|
820 |
+
The route descriptor is an object with the following defined properties.
|
821 |
+
|
822 |
+
* `supports`: A JSON array of supported HTTP methods (verbs). Possible values
|
823 |
+
are "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"
|
824 |
+
* `accepts_json`: A boolean indicating whether data can be passed directly via a
|
825 |
+
POST request body. Default for missing properties is false.
|
826 |
+
* `meta`: An Entity Meta entity. Typical `links` values consist of a `self` link
|
827 |
+
pointing to the route's full URL.
|
828 |
+
|
829 |
+
### `meta`
|
830 |
+
The `meta` field is a Entity Meta entity with metadata relating to the entity
|
831 |
+
representation.
|
832 |
+
|
833 |
+
Typical `links` values for the meta object consist of a `help` key with the
|
834 |
+
value indicating a human-readable documentation page about the API.
|
835 |
+
|
836 |
+
### Example
|
837 |
+
|
838 |
+
{
|
839 |
+
"name": "My WordPress Site",
|
840 |
+
"description": "Just another WordPress site",
|
841 |
+
"URL": "http:\/\/example.com",
|
842 |
+
"routes": {
|
843 |
+
"\/": {
|
844 |
+
"supports": [
|
845 |
+
"HEAD",
|
846 |
+
"GET"
|
847 |
+
],
|
848 |
+
"meta": {
|
849 |
+
"self": "http:\/\/example.com\/wp-json\/"
|
850 |
+
}
|
851 |
+
},
|
852 |
+
"\/posts": {
|
853 |
+
"supports": [
|
854 |
+
"HEAD",
|
855 |
+
"GET",
|
856 |
+
"POST"
|
857 |
+
],
|
858 |
+
"meta": {
|
859 |
+
"self": "http:\/\/example.com\/wp-json\/posts"
|
860 |
+
},
|
861 |
+
"accepts_json": true
|
862 |
+
},
|
863 |
+
"\/posts\/<id>": {
|
864 |
+
"supports": [
|
865 |
+
"HEAD",
|
866 |
+
"GET",
|
867 |
+
"POST",
|
868 |
+
"PUT",
|
869 |
+
"PATCH",
|
870 |
+
"DELETE"
|
871 |
+
],
|
872 |
+
"accepts_json": true
|
873 |
+
},
|
874 |
+
"\/posts\/<id>\/revisions": {
|
875 |
+
"supports": [
|
876 |
+
"HEAD",
|
877 |
+
"GET"
|
878 |
+
]
|
879 |
+
},
|
880 |
+
"\/posts\/<id>\/comments": {
|
881 |
+
"supports": [
|
882 |
+
"HEAD",
|
883 |
+
"GET",
|
884 |
+
"POST"
|
885 |
+
],
|
886 |
+
"accepts_json": true
|
887 |
+
},
|
888 |
+
"\/posts\/<id>\/comments\/<comment>": {
|
889 |
+
"supports": [
|
890 |
+
"HEAD",
|
891 |
+
"GET",
|
892 |
+
"POST",
|
893 |
+
"PUT",
|
894 |
+
"PATCH",
|
895 |
+
"DELETE"
|
896 |
+
],
|
897 |
+
"accepts_json": true
|
898 |
+
},
|
899 |
+
},
|
900 |
+
"meta": {
|
901 |
+
"links": {
|
902 |
+
"help": "https:\/\/github.com\/WP-API\/WP-API",
|
903 |
+
"profile": "https:\/\/raw.github.com\/WP-API\/WP-API\/master\/docs\/schema.json"
|
904 |
+
}
|
905 |
+
}
|
906 |
+
}
|
907 |
+
|
908 |
+
Post
|
909 |
+
----
|
910 |
+
The Post entity is a JSON object of post properties. Unless otherwise defined,
|
911 |
+
properties are available in all contexts. The following properties are defined
|
912 |
+
for the Post entity object:
|
913 |
+
|
914 |
+
### `title`
|
915 |
+
The `title` field is a string with the post's title.
|
916 |
+
|
917 |
+
### `date`, `date_gmt`
|
918 |
+
The `date` and `date_gmt` fields are strings with the post's creation date and
|
919 |
+
time in the local time and UTC respectively. These fields follow the [RFC3339][]
|
920 |
+
Section 5.6 datetime representation.
|
921 |
+
|
922 |
+
date = date-time
|
923 |
+
date_gmt = date-time
|
924 |
+
|
925 |
+
[RFC3339]: http://tools.ietf.org/html/rfc3339
|
926 |
+
|
927 |
+
### `modified`, `modified_gmt`
|
928 |
+
The `modified` and `modified_gmt` fields are strings with the post's last
|
929 |
+
modification date and time in the local time and UTC respectively. These fields
|
930 |
+
follow the [RFC3339][] Section 5.6 datetime representation.
|
931 |
+
|
932 |
+
modified = date-time
|
933 |
+
modified_gmt = date-time
|
934 |
+
|
935 |
+
### `date_tz`, `modified_tz`
|
936 |
+
The `date_tz` and `modified_tz` fields are strings with the timezone applying to
|
937 |
+
the `date` and `modified` fields respectively. The timezone is a [Olsen zoneinfo
|
938 |
+
database][] identifier. While the `date` and `modified` fields include timezone
|
939 |
+
offset information, the `date_tz` and `modified_tz` fields allow proper data
|
940 |
+
operations across Daylight Savings Time boundaries.
|
941 |
+
|
942 |
+
Note that in addition to the normal Olsen timezones, manual offsets may be
|
943 |
+
given. These manual offsets use the deprecated `Etc/GMT+...` zones and specify
|
944 |
+
an integer offset in hours from UTC.
|
945 |
+
|
946 |
+
timezone = Olsen-timezone / manual-offset
|
947 |
+
manual-offset = "Etc/GMT" ("-" / "+") 1*2( DIGIT )
|
948 |
+
|
949 |
+
Consumers SHOULD use the fields if they perform mathematical operations on the
|
950 |
+
`date` and `modified` fields (such as adding an hour to the last modification
|
951 |
+
date) rather than relying on the `time-offset` in the `date` or
|
952 |
+
`modified` fields.
|
953 |
+
|
954 |
+
[Olsen zoneinfo database]: https://en.wikipedia.org/wiki/Tz_database
|
955 |
+
|
956 |
+
### `status`
|
957 |
+
The `status` field is a string with the post's status. This status relates to
|
958 |
+
where the post is in the editorial process. These are usually set values, but
|
959 |
+
some providers may have extra post statuses.
|
960 |
+
|
961 |
+
post-status = "draft" / "pending" / "private" / "publish" / "trash" / token
|
962 |
+
|
963 |
+
Consumers who encounter an unknown or missing post status SHOULD treat it the
|
964 |
+
same as a "draft" status.
|
965 |
+
|
966 |
+
### `type`
|
967 |
+
The `type` field is a string with the post's type. This field is specific to
|
968 |
+
providers, with the most basic representation being "post". The type of the
|
969 |
+
post usually relates to the fields in the Post entity, with other types having
|
970 |
+
additional fields specific to the type.
|
971 |
+
|
972 |
+
post-type = "post" / token
|
973 |
+
|
974 |
+
Consumers who encounter an unknown or missing post type SHOULD treat it the same
|
975 |
+
as a "post" type.
|
976 |
+
|
977 |
+
### `name`
|
978 |
+
The `name` field is a string with the post's slug.
|
979 |
+
|
980 |
+
### `author`
|
981 |
+
The `author` field is a User entity with the user who created the post.
|
982 |
+
|
983 |
+
### `password`
|
984 |
+
The `password` field is a string with the post's password. A zero-length
|
985 |
+
password indicates that the post does not have a password.
|
986 |
+
|
987 |
+
Consumers who encounter a missing password MUST treat it the same as a
|
988 |
+
zero-length password.
|
989 |
+
|
990 |
+
### `content`
|
991 |
+
The `content` field is a string with the post's content.
|
992 |
+
|
993 |
+
### `excerpt`
|
994 |
+
The `excerpt` field is a string with the post's excerpt. This is usually a
|
995 |
+
shortened version of the post content, suitable for displaying in
|
996 |
+
collection views.
|
997 |
+
|
998 |
+
Consumers who encounter a missing excerpt MAY present a shortened version of the
|
999 |
+
`content` field instead.
|
1000 |
+
|
1001 |
+
### `content_raw`, `excerpt_raw`
|
1002 |
+
The `content_raw` and `excerpt_raw` fields are strings with the post's content
|
1003 |
+
and excerpt respectively. Unlike the `content` and `excerpt` fields, the value
|
1004 |
+
has not been passed through internal filtering, and is suitable for editing.
|
1005 |
+
|
1006 |
+
(Context Availability: `edit`)
|
1007 |
+
|
1008 |
+
### `parent`
|
1009 |
+
The `parent` field is an integer or JSON object with the post's parent
|
1010 |
+
post ID. A literal zero indicates that the post does not have a parent
|
1011 |
+
post.
|
1012 |
+
|
1013 |
+
post-parent = "0" / 1*DIGIT
|
1014 |
+
|
1015 |
+
Consumers who encounter a missing parent ID MUST treat it the same as a parent
|
1016 |
+
post ID of 0.
|
1017 |
+
|
1018 |
+
Parent fields will be expanded into a full Post entity in the `view` or `edit`
|
1019 |
+
contexts, but only one level deep. The embedded Post entity will be rendered
|
1020 |
+
using the `parent` context.
|
1021 |
+
|
1022 |
+
In the `parent` context, the field will contain an integer with the post's
|
1023 |
+
parent post ID as above.
|
1024 |
+
|
1025 |
+
### `link`
|
1026 |
+
The `link` field is a string with the full URL to the post's canonical view.
|
1027 |
+
This is typically the human-readable location of the entity.
|
1028 |
+
|
1029 |
+
### `guid`
|
1030 |
+
The `guid` field is a string with the post's globally unique identifier (GUID).
|
1031 |
+
|
1032 |
+
The GUID is typically in URL form, as this is a relatively easy way of ensuring
|
1033 |
+
that the GUID is globally unique. However, consumers MUST NOT treat the GUID as
|
1034 |
+
a URL, and MUST treat the GUID as a string of arbitrary characters.
|
1035 |
+
|
1036 |
+
### `menu_order`
|
1037 |
+
The `menu_order` field is an integer with the post's sorting position. This is
|
1038 |
+
typically used to affect sorting when displaying the post in menus or lists.
|
1039 |
+
Larger integers should be treated as sorting before smaller integers.
|
1040 |
+
|
1041 |
+
menu-order = 1*DIGIT / "-" 1*DIGIT
|
1042 |
+
|
1043 |
+
Consumers who encounter a missing sorting position MUST treat it the same as a
|
1044 |
+
sorting position of 0.
|
1045 |
+
|
1046 |
+
### `comment_status`
|
1047 |
+
The `comment_status` field is a string with the post's current commenting
|
1048 |
+
status. This field indicates whether users can submit comments to the post.
|
1049 |
+
|
1050 |
+
post-comment-status = "open" / "closed" / token
|
1051 |
+
|
1052 |
+
Providers MAY use statuses other than "open" or "closed" to indicate other
|
1053 |
+
statuses. Consumers who encounter an unknown or missing comment status SHOULD
|
1054 |
+
treat it as "closed".
|
1055 |
+
|
1056 |
+
### `ping_status`
|
1057 |
+
The `ping_status` field is a string with the post's current pingback/trackback
|
1058 |
+
status. This field indicates whether users can submit pingbacks or trackbacks
|
1059 |
+
to the post.
|
1060 |
+
|
1061 |
+
ping-status = "open" / "closed" / token
|
1062 |
+
|
1063 |
+
Providers MAY use statuses other than "open" or "closed" to indicate other
|
1064 |
+
statuses. Consumers who encounter an unknown or missing ping status SHOULD treat
|
1065 |
+
it as "closed".
|
1066 |
+
|
1067 |
+
### `sticky`
|
1068 |
+
The `sticky` field is a boolean indicating whether the post is marked as a
|
1069 |
+
sticky post. Consumers typically display sticky posts before other posts in
|
1070 |
+
collection views.
|
1071 |
+
|
1072 |
+
### `post_thumbnail`
|
1073 |
+
The `post_thumbnail` field is a Media entity.
|
1074 |
+
|
1075 |
+
### `post_format`
|
1076 |
+
The `post_format` field is a string with the post format. The post format
|
1077 |
+
indicates how some meta fields should be displayed. For example, posts with the
|
1078 |
+
"link" format may wish to display an extra link to a URL specified in a meta
|
1079 |
+
field or emphasise a link in the post content.
|
1080 |
+
|
1081 |
+
post-format = "standard" / "aside" / "gallery" / "image" / "link" / "status" / "quote" / "video" / "audio" / "chat"
|
1082 |
+
|
1083 |
+
Providers MUST NOT use post formats not specified by this specification, unless
|
1084 |
+
specified in a subsequent version of the specification. Consumers MUST treat
|
1085 |
+
unknown post formats as "standard".
|
1086 |
+
|
1087 |
+
### `terms`
|
1088 |
+
The `terms` field is a Term collection.
|
1089 |
+
|
1090 |
+
### `post_meta`
|
1091 |
+
The `meta` field is a Metadata entity with metadata relating to the post.
|
1092 |
+
|
1093 |
+
### `meta`
|
1094 |
+
The `meta` field is a Entity Meta entity with metadata relating to the entity
|
1095 |
+
representation.
|
1096 |
+
|
1097 |
+
### Example
|
1098 |
+
|
1099 |
+
{
|
1100 |
+
"ID": 1,
|
1101 |
+
"title": "Hello world!q",
|
1102 |
+
"status": "publish",
|
1103 |
+
"type": "post",
|
1104 |
+
"author": {
|
1105 |
+
"ID": 1,
|
1106 |
+
"name": "admin",
|
1107 |
+
"slug": "admin",
|
1108 |
+
"URL": "",
|
1109 |
+
"avatar": "http:\/\/0.gravatar.com\/avatar\/c57c8945079831fa3c19caef02e44614&d=404&r=G",
|
1110 |
+
"meta": {
|
1111 |
+
"links": {
|
1112 |
+
"self": "http:\/\/example.com\/wp-json\/users\/1",
|
1113 |
+
"archives": "http:\/\/example.com\/wp-json\/users\/1\/posts"
|
1114 |
+
}
|
1115 |
+
},
|
1116 |
+
"first_name": "",
|
1117 |
+
"last_name": ""
|
1118 |
+
},
|
1119 |
+
"content": "<p>Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!<\/p>\n",
|
1120 |
+
"parent": 0,
|
1121 |
+
"link": "http:\/\/example.com\/2013\/06\/02\/hello-world\/",
|
1122 |
+
"date": "2013-06-02T05:28:00+10:00",
|
1123 |
+
"modified": "2013-06-30T13:56:57+10:00",
|
1124 |
+
"format": "standard",
|
1125 |
+
"slug": "hello-world",
|
1126 |
+
"guid": "http:\/\/example.com\/?p=1",
|
1127 |
+
"excerpt": "",
|
1128 |
+
"menu_order": 0,
|
1129 |
+
"comment_status": "open",
|
1130 |
+
"ping_status": "open",
|
1131 |
+
"sticky": false,
|
1132 |
+
"date_tz": "Australia\/Brisbane",
|
1133 |
+
"date_gmt": "2013-06-02T05:28:00+00:00",
|
1134 |
+
"modified_tz": "Australia\/Brisbane",
|
1135 |
+
"modified_gmt": "2013-06-30T03:56:57+00:00",
|
1136 |
+
"password": "",
|
1137 |
+
"post_meta": [
|
1138 |
+
],
|
1139 |
+
"meta": {
|
1140 |
+
"links": {
|
1141 |
+
"self": "http:\/\/example.com\/wp-json\/posts\/1",
|
1142 |
+
"author": "http:\/\/example.com\/wp-json\/users\/1",
|
1143 |
+
"collection": "http:\/\/example.com\/wp-json\/posts",
|
1144 |
+
"replies": "http:\/\/example.com\/wp-json\/posts\/1\/comments",
|
1145 |
+
"version-history": "http:\/\/example.com\/wp-json\/posts\/1\/revisions"
|
1146 |
+
}
|
1147 |
+
},
|
1148 |
+
"featured_image": null,
|
1149 |
+
"terms": {
|
1150 |
+
"category": {
|
1151 |
+
"ID": 1,
|
1152 |
+
"name": "Uncategorized",
|
1153 |
+
"slug": "uncategorized",
|
1154 |
+
"parent": null,
|
1155 |
+
"count": 7,
|
1156 |
+
"meta": {
|
1157 |
+
"links": {
|
1158 |
+
"collection": "http:\/\/example.com\/wp-json\/taxonomies\/category\/terms",
|
1159 |
+
"self": "http:\/\/example.com\/wp-json\/taxonomies\/category\/terms\/1"
|
1160 |
+
}
|
1161 |
+
}
|
1162 |
+
}
|
1163 |
+
}
|
1164 |
+
}
|
1165 |
+
|
1166 |
+
|
1167 |
+
|
1168 |
+
Entity Meta
|
1169 |
+
-----------
|
1170 |
+
The Entity Meta entity is a JSON object with custom metadata relating to the
|
1171 |
+
representation of the parent entity.
|
1172 |
+
|
1173 |
+
The following properties are defined for the Entity Meta entity object:
|
1174 |
+
|
1175 |
+
### `links`
|
1176 |
+
The `links` field is a JSON object with hyperlinks to related entities. Each
|
1177 |
+
item's key is a link relation as per the [IANA Link Relations registry][] with
|
1178 |
+
the value of the item being the corresponding link URL.
|
1179 |
+
|
1180 |
+
Typical link relations are:
|
1181 |
+
|
1182 |
+
* `self`: A URL pointing to the current entity's location.
|
1183 |
+
* `up`: A URL pointing to the parent entity's location.
|
1184 |
+
* `collection`: A URL pointing to a collection that the entity is a member of.
|
1185 |
+
|
1186 |
+
[IANA Link Relations registry]: http://www.iana.org/assignments/link-relations/link-relations.xml
|
1187 |
+
|
1188 |
+
|
1189 |
+
User
|
1190 |
+
----
|
1191 |
+
The User entity is a JSON object with user properties. The following properties
|
1192 |
+
are defined for the User entity object:
|
1193 |
+
|
1194 |
+
### `ID`
|
1195 |
+
The `ID` field is an integer with the user's ID.
|
1196 |
+
|
1197 |
+
### `name`
|
1198 |
+
The `name` field is a string with the user's display name.
|
1199 |
+
|
1200 |
+
### `slug`
|
1201 |
+
The `slug` field is a string with the user's slug.
|
1202 |
+
|
1203 |
+
### `URL`
|
1204 |
+
The `URL` field is a string with the URL to the author's site. This is typically
|
1205 |
+
an external link of the author's choice.
|
1206 |
+
|
1207 |
+
### `avatar`
|
1208 |
+
The `avatar` field is a string with the URL to the author's avatar image.
|
1209 |
+
|
1210 |
+
Providers SHOULD ensure that for users without an avatar image, this field is
|
1211 |
+
either zero-length or the URL returns a HTTP 404 error code on access. Consumers
|
1212 |
+
MAY display a default avatar instead of a zero-length or URL which returns
|
1213 |
+
a HTTP 404 error code.
|
1214 |
+
|
1215 |
+
### `meta`
|
1216 |
+
The `meta` field is a Entity Meta entity with metadata relating to the entity
|
1217 |
+
representation.
|
1218 |
+
|
1219 |
+
|
1220 |
+
Metadata
|
1221 |
+
--------
|
1222 |
+
The Metadata entity is a JSON array with metadata fields. Each metadata field is
|
1223 |
+
a JSON object with `id`, `key` and `value` fields.
|
1224 |
+
|
1225 |
+
### `id`
|
1226 |
+
The `id` field of the metadata field is a positive integer with the internal
|
1227 |
+
metadata ID.
|
1228 |
+
|
1229 |
+
### `key`
|
1230 |
+
The `key` field of the metadata field is a string with the metadata field name.
|
1231 |
+
|
1232 |
+
### `value`
|
1233 |
+
The `value` field of the metadata field is a string with the metadata
|
1234 |
+
field value.
|
1235 |
+
|
1236 |
+
|
1237 |
+
Comment
|
1238 |
+
-------
|
1239 |
+
The Comment entity is a JSON object with comment properties. The following
|
1240 |
+
properties are defined for the Comment entity object:
|
1241 |
+
|
1242 |
+
### `ID`
|
1243 |
+
The `ID` field is an integer with the comment's ID.
|
1244 |
+
|
1245 |
+
### `content`
|
1246 |
+
The `content` field is a string with the comment's content.
|
1247 |
+
|
1248 |
+
### `status`
|
1249 |
+
The `status` field is a string with the comment's status. This field indicates
|
1250 |
+
whether the comment is in the publishing process, or if it has been deleted or
|
1251 |
+
marked as spam.
|
1252 |
+
|
1253 |
+
comment-status = "hold" / "approved" / "spam" / "trash" / token
|
1254 |
+
|
1255 |
+
Providers MAY use other values to indicate other statuses. Consumers who
|
1256 |
+
encounter an unknown or missing status SHOULD treat it as "hold".
|
1257 |
+
|
1258 |
+
### `type`
|
1259 |
+
The `type` field is a string with the comment's type. This is usually one of the
|
1260 |
+
following, but providers may provide additional values.
|
1261 |
+
|
1262 |
+
comment-type = "comment" / "trackback" / "pingback" / token
|
1263 |
+
|
1264 |
+
Providers MAY use other values to indicate other types. Consumers who encounter
|
1265 |
+
an unknown or missing status SHOULD treat it as "comment".
|
1266 |
+
|
1267 |
+
### `post`
|
1268 |
+
The `post` field is an integer with the parent post for the comment, or a Post
|
1269 |
+
entity describing the parent post. A literal zero indicates that the comment
|
1270 |
+
does not have a parent post.
|
1271 |
+
|
1272 |
+
comment-post-parent = "0" / 1*DIGIT
|
1273 |
+
|
1274 |
+
Consumers who encounter a missing post ID MUST treat it the same as a parent
|
1275 |
+
post ID of 0.
|
1276 |
+
|
1277 |
+
### `parent`
|
1278 |
+
The `post` field is an integer with the parent comment, or a Comment entity
|
1279 |
+
describing the parent comment. A literal zero indicates that the comment does
|
1280 |
+
not have a parent comment.
|
1281 |
+
|
1282 |
+
comment-parent = "0" / 1*DIGIT
|
1283 |
+
|
1284 |
+
Consumers who encounter a missing parent ID MUST treat it the same as a parent
|
1285 |
+
comment ID of 0.
|
1286 |
+
|
1287 |
+
### `author`
|
1288 |
+
The `author` field is a User entity with the comment author's data, or a
|
1289 |
+
User-like object for anonymous authors. The User-like object contains the
|
1290 |
+
following properties:
|
1291 |
+
|
1292 |
+
#### `ID`
|
1293 |
+
The `ID` property on the User-like object is always set to `0` for anonymous
|
1294 |
+
authors.
|
1295 |
+
|
1296 |
+
#### `name`
|
1297 |
+
The `name` property on the User-like object is a string with the author's name.
|
1298 |
+
|
1299 |
+
#### `URL`
|
1300 |
+
The `URL` property on the User-like object is a string with the author's URL.
|
1301 |
+
|
1302 |
+
#### `avatar`
|
1303 |
+
The `avatar` property on the User-like object is a string with the URL to the
|
1304 |
+
author's avatar image.
|
1305 |
+
|
1306 |
+
This property should be treated the same as the avatar property on the
|
1307 |
+
User entity.
|
1308 |
+
|
1309 |
+
|
1310 |
+
### `date`, `date_gmt`
|
1311 |
+
The `date` and `date_gmt` fields are strings with the post's creation date and
|
1312 |
+
time in the local time and UTC respectively. These fields follow the [RFC3339][]
|
1313 |
+
Section 5.6 datetime representation.
|
1314 |
+
|
1315 |
+
date = date-time
|
1316 |
+
date_gmt = date-time
|
1317 |
+
|
1318 |
+
This field should be treated the same as the `date` and `date_gmt` properties on
|
1319 |
+
a Post entity.
|
1320 |
+
|
1321 |
+
[RFC3339]: http://tools.ietf.org/html/rfc3339
|
1322 |
+
|
1323 |
+
### `date_tz`, `modified_tz`
|
1324 |
+
The `date_tz` and `modified_tz` fields are strings with the timezone applying to
|
1325 |
+
the `date` and `modified` fields respectively. The timezone is a [Olsen zoneinfo
|
1326 |
+
database][] identifier. While the `date` field includes timezone offset
|
1327 |
+
information, the `date_tz` field allows proper data operations across Daylight
|
1328 |
+
Savings Time boundaries.
|
1329 |
+
|
1330 |
+
This field should be treated the same as the `date_tz` property on a
|
1331 |
+
Post entity.
|
1332 |
+
|
1333 |
+
|
1334 |
+
Media
|
1335 |
+
-----
|
1336 |
+
The Media entity is a JSON object based on the Post entity. It contains all
|
1337 |
+
properties of the Post entity, with the following additional properties defined:
|
1338 |
+
|
1339 |
+
### `source`
|
1340 |
+
The `source` field is a string with the URL of the entity's original file. For
|
1341 |
+
image media, this is the source file that intermediate representations are
|
1342 |
+
generated from. For non-image media, this is the attached media file itself.
|
1343 |
+
|
1344 |
+
### `is_image`
|
1345 |
+
The `is_image` field is a boolean which indicates whether the entity's
|
1346 |
+
associated file should be handled as an image.
|
1347 |
+
|
1348 |
+
### `attachment_meta`
|
1349 |
+
The `attachment_meta` field is a Media Meta entity. If the file is not an image
|
1350 |
+
(as indicated by the `is_image` field), this is an empty JSON object.
|
1351 |
+
|
1352 |
+
|
1353 |
+
Media Meta
|
1354 |
+
----------
|
1355 |
+
The Media Meta entity is a JSON object with properties relating to the
|
1356 |
+
associated Media entity. The following properties are defined for the entity:
|
1357 |
+
|
1358 |
+
### `width`
|
1359 |
+
The `width` field is an integer with the original file's width in pixels.
|
1360 |
+
|
1361 |
+
### `height`
|
1362 |
+
The `height` field is an integer with the original file's height in pixels.
|
1363 |
+
|
1364 |
+
### `file`
|
1365 |
+
The `file` field is a string with the path to the original file, relative to the
|
1366 |
+
site's upload directory.
|
1367 |
+
|
1368 |
+
### `sizes`
|
1369 |
+
The `sizes` field is a JSON object mapping intermediate image sizes to image
|
1370 |
+
data objects. The key of each item is the size of the intermediate image as an
|
1371 |
+
internal string representation. The value of each item has the following
|
1372 |
+
properties defined.
|
1373 |
+
|
1374 |
+
* `file`: The filename of the intermediate file, relative to the directory of
|
1375 |
+
the original file.
|
1376 |
+
* `width`: The width of the intermediate file in pixels.
|
1377 |
+
* `height`: The height of the intermediate file in pixels.
|
1378 |
+
* `mime-type`: The MIME type of the intermediate file.
|
1379 |
+
* `url`: The full URL to the intermediate file.
|
1380 |
+
|
1381 |
+
### `image_meta`
|
1382 |
+
The `image_meta` field is a JSON object mapping image meta properties to their
|
1383 |
+
values. This data is taken from the EXIF data on the original image. The
|
1384 |
+
following properties are defined.
|
1385 |
+
|
1386 |
+
* `aperture`: The aperture used to create the original image as a decimal number
|
1387 |
+
(with two decimal places).
|
1388 |
+
* `credit`: Credit for the original image.
|
1389 |
+
* `camera`: The camera used to create the original image.
|
1390 |
+
* `created_timestamp`: When the file was created, as a Unix timestamp.
|
1391 |
+
* `copyright`: Copyright for the original image.
|
1392 |
+
* `focal_length`: The focal length used to create the original image as a
|
1393 |
+
decimal string.
|
1394 |
+
* `iso`: The ISO used to create the original image.
|
1395 |
+
* `shutter_speed`: The shutter speed used to create the original image, as a
|
1396 |
+
decimal string.
|
1397 |
+
* `title`: The original title of the image.
|
1398 |
+
|
1399 |
+
|
1400 |
+
Documents
|
1401 |
+
=========
|
1402 |
+
|
1403 |
+
Index
|
1404 |
+
-----
|
1405 |
+
The Index document is the root endpoint for the API server and describes the
|
1406 |
+
contents and abilities of the API server.
|
1407 |
+
|
1408 |
+
### Body
|
1409 |
+
The body of an Index document is an Index entity.
|
1410 |
+
|
1411 |
+
### Example
|
1412 |
+
|
1413 |
+
{
|
1414 |
+
"name":"My WordPress Site",
|
1415 |
+
"description":"Just another WordPress site",
|
1416 |
+
"URL":"http:\/\/example.com",
|
1417 |
+
"routes": {
|
1418 |
+
"\/": {
|
1419 |
+
"supports": [ "HEAD", "GET" ]
|
1420 |
+
},
|
1421 |
+
"\/posts": {
|
1422 |
+
"supports": [ "HEAD", "GET", "POST" ],
|
1423 |
+
"accepts_json": true
|
1424 |
+
},
|
1425 |
+
"\/posts\/<id>": {
|
1426 |
+
"supports": [ "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" ]
|
1427 |
+
},
|
1428 |
+
"\/posts\/<id>\/revisions": {
|
1429 |
+
"supports": [ "HEAD", "GET" ]
|
1430 |
+
},
|
1431 |
+
"\/posts\/<id>\/comments": {
|
1432 |
+
"supports": [ "HEAD", "GET", "POST" ],
|
1433 |
+
"accepts_json":true
|
1434 |
+
}
|
1435 |
+
},
|
1436 |
+
"meta": {
|
1437 |
+
"links": {
|
1438 |
+
"help":"http:\/\/codex.wordpress.org\/JSON_API"
|
1439 |
+
}
|
1440 |
+
}
|
1441 |
+
}
|
1442 |
+
|
1443 |
+
|
1444 |
+
Post
|
1445 |
+
----
|
1446 |
+
A Post document is defined as the representation of a post item, analogous to an
|
1447 |
+
Atom item.
|
1448 |
+
|
1449 |
+
### Headers
|
1450 |
+
The following headers are sent when a Post is the main entity:
|
1451 |
+
|
1452 |
+
* `Link`:
|
1453 |
+
* `rel="alternate"; type=text/html`: The permalink for the Post
|
1454 |
+
* `rel="collection"`: The endpoint of the Post Collection the Post is
|
1455 |
+
contained in
|
1456 |
+
* `rel="replies"`: The endpoint of the associated Comment Collection
|
1457 |
+
* `rel="version-history"`: The endpoint of the Post Collection containing
|
1458 |
+
the revisions of the Post
|
1459 |
+
|
1460 |
+
|
1461 |
+
### Body
|
1462 |
+
The body of a Post document is a Post entity.
|
1463 |
+
|
1464 |
+
|
1465 |
+
### Example
|
1466 |
+
|
1467 |
+
HTTP/1.1 200 OK
|
1468 |
+
Date: Mon, 07 Jan 2013 03:35:14 GMT
|
1469 |
+
Last-Modified: Mon, 07 Jan 2013 03:35:14 GMT
|
1470 |
+
Link: <http://localhost/wptrunk/?p=1>; rel="alternate"; type=text/html
|
1471 |
+
Link: <http://localhost/wptrunk/wp-json/users/1>; rel="author"
|
1472 |
+
Link: <http://localhost/wptrunk/wp-json/posts>; rel="collection"
|
1473 |
+
Link: <http://localhost/wptrunk/wp-json/posts/158/comments>; rel="replies"
|
1474 |
+
Link: <http://localhost/wptrunk/wp-json/posts/158/revisions>; rel="version-history"
|
1475 |
+
Content-Type: application/json; charset=UTF-8
|
1476 |
+
|
1477 |
+
{
|
1478 |
+
"ID":158,
|
1479 |
+
"title":"This is a test!",
|
1480 |
+
"status":"publish",
|
1481 |
+
"type":"post",
|
1482 |
+
"author":{
|
1483 |
+
"ID":1,
|
1484 |
+
"name":"admin",
|
1485 |
+
"slug":"admin",
|
1486 |
+
"URL":"",
|
1487 |
+
"avatar":"http:\/\/0.gravatar.com\/avatar\/c57c8945079831fa3c19caef02e44614&d=404&r=G",
|
1488 |
+
"meta":{
|
1489 |
+
"links":{
|
1490 |
+
"self":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1",
|
1491 |
+
"archives":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1\/posts"
|
1492 |
+
}
|
1493 |
+
}
|
1494 |
+
},
|
1495 |
+
"content":"Hello.\r\n\r\nHah.",
|
1496 |
+
"parent":0,
|
1497 |
+
"link":"http:\/\/localhost\/wptrunk\/158\/this-is-a-test\/",
|
1498 |
+
"date":"2013-01-07T13:35:14+10:00",
|
1499 |
+
"modified":"2013-01-07T13:49:40+10:00",
|
1500 |
+
"format":"standard",
|
1501 |
+
"slug":"this-is-a-test",
|
1502 |
+
"guid":"http:\/\/localhost\/wptrunk\/?p=158",
|
1503 |
+
"excerpt":"",
|
1504 |
+
"menu_order":0,
|
1505 |
+
"comment_status":"open",
|
1506 |
+
"ping_status":"open",
|
1507 |
+
"sticky":false,
|
1508 |
+
"date_tz":"Australia\/Brisbane",
|
1509 |
+
"date_gmt":"2013-01-07T03:35:14+00:00",
|
1510 |
+
"modified_tz":"Australia\/Brisbane",
|
1511 |
+
"modified_gmt":"2013-01-07T03:49:40+00:00",
|
1512 |
+
"post_thumbnail":[],
|
1513 |
+
"terms":{
|
1514 |
+
"category":{
|
1515 |
+
"ID":1,
|
1516 |
+
"name":"Uncategorized",
|
1517 |
+
"slug":"uncategorized",
|
1518 |
+
"group":0,
|
1519 |
+
"parent":0,
|
1520 |
+
"count":4,
|
1521 |
+
"meta":{
|
1522 |
+
"links":{
|
1523 |
+
"collection":"http:\/\/localhost\/wptrunk\/wp-json\/taxonomy\/category",
|
1524 |
+
"self":"http:\/\/localhost\/wptrunk\/wp-json\/taxonomy\/category\/terms\/1"
|
1525 |
+
}
|
1526 |
+
}
|
1527 |
+
}
|
1528 |
+
},
|
1529 |
+
"post_meta":[],
|
1530 |
+
"meta":{
|
1531 |
+
"links":{
|
1532 |
+
"self":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158",
|
1533 |
+
"author":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1",
|
1534 |
+
"collection":"http:\/\/localhost\/wptrunk\/wp-json\/posts",
|
1535 |
+
"replies":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158\/comments",
|
1536 |
+
"version-history":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158\/revisions"
|
1537 |
+
}
|
1538 |
+
}
|
1539 |
+
}
|
1540 |
+
|
1541 |
+
|
1542 |
+
Post Collection
|
1543 |
+
---------------
|
1544 |
+
A Post Collection document is defined as a collection of Post entities.
|
1545 |
+
|
1546 |
+
### Headers
|
1547 |
+
The following headers are sent when a Post Collection is the main entity:
|
1548 |
+
|
1549 |
+
* `Link`:
|
1550 |
+
* `rel="item"` - Each item in the collection has a corresponding Link header
|
1551 |
+
containing the location of the endpoint for that resource.
|
1552 |
+
|
1553 |
+
|
1554 |
+
### Body
|
1555 |
+
The Post Collection document is a JSON array of Post entities.
|
1556 |
+
|
1557 |
+
|
1558 |
+
User
|
1559 |
+
----
|
1560 |
+
The User document describes a member of the site.
|
1561 |
+
|
1562 |
+
### Body
|
1563 |
+
The body of a User document is a User entity.
|
1564 |
+
|
1565 |
+
|
1566 |
+
Appendix A: JSON Schema
|
1567 |
+
=======================
|
1568 |
+
The JSON Schema describing the entities in this document is available in
|
1569 |
+
schema.json.
|
extras.php
ADDED
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Extra File where a lot of the extra functions from plugin.php go.
|
5 |
+
*
|
6 |
+
* @package WordPress
|
7 |
+
* @subpackage JSON API
|
8 |
+
*
|
9 |
+
* @TODO fix this doc block (Make it better maybe?)
|
10 |
+
*/
|
11 |
+
|
12 |
+
add_action( 'wp_enqueue_scripts', 'rest_register_scripts', -100 );
|
13 |
+
add_action( 'admin_enqueue_scripts', 'rest_register_scripts', -100 );
|
14 |
+
add_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' );
|
15 |
+
add_action( 'wp_head', 'rest_output_link_wp_head', 10, 0 );
|
16 |
+
add_action( 'template_redirect', 'rest_output_link_header', 11, 0 );
|
17 |
+
add_action( 'auth_cookie_malformed', 'rest_cookie_collect_status' );
|
18 |
+
add_action( 'auth_cookie_expired', 'rest_cookie_collect_status' );
|
19 |
+
add_action( 'auth_cookie_bad_username', 'rest_cookie_collect_status' );
|
20 |
+
add_action( 'auth_cookie_bad_hash', 'rest_cookie_collect_status' );
|
21 |
+
add_action( 'auth_cookie_valid', 'rest_cookie_collect_status' );
|
22 |
+
add_filter( 'rest_authentication_errors', 'rest_cookie_check_errors', 100 );
|
23 |
+
|
24 |
+
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Register API Javascript helpers.
|
28 |
+
*
|
29 |
+
* @see wp_register_scripts()
|
30 |
+
*/
|
31 |
+
function rest_register_scripts() {
|
32 |
+
wp_register_script( 'wp-api', plugins_url( 'wp-api.js', __FILE__ ), array( 'jquery', 'backbone', 'underscore' ), '1.1', true );
|
33 |
+
|
34 |
+
$settings = array( 'root' => esc_url_raw( get_rest_url() ), 'nonce' => wp_create_nonce( 'wp_rest' ) );
|
35 |
+
wp_localize_script( 'wp-api', 'WP_API_Settings', $settings );
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Add the API URL to the WP RSD endpoint.
|
40 |
+
*/
|
41 |
+
function rest_output_rsd() {
|
42 |
+
$api_root = get_rest_url();
|
43 |
+
|
44 |
+
if ( empty( $api_root ) ) {
|
45 |
+
return;
|
46 |
+
}
|
47 |
+
?>
|
48 |
+
<api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
|
49 |
+
<?php
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Output API link tag into page header.
|
54 |
+
*
|
55 |
+
* @see get_rest_url()
|
56 |
+
*/
|
57 |
+
function rest_output_link_wp_head() {
|
58 |
+
$api_root = get_rest_url();
|
59 |
+
|
60 |
+
if ( empty( $api_root ) ) {
|
61 |
+
return;
|
62 |
+
}
|
63 |
+
|
64 |
+
echo "<link rel='https://github.com/WP-API/WP-API' href='" . esc_url( $api_root ) . "' />\n";
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Send a Link header for the API.
|
69 |
+
*/
|
70 |
+
function rest_output_link_header() {
|
71 |
+
if ( headers_sent() ) {
|
72 |
+
return;
|
73 |
+
}
|
74 |
+
|
75 |
+
$api_root = get_rest_url();
|
76 |
+
|
77 |
+
if ( empty($api_root) ) {
|
78 |
+
return;
|
79 |
+
}
|
80 |
+
|
81 |
+
header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://github.com/WP-API/WP-API"', false );
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Check for errors when using cookie-based authentication.
|
86 |
+
*
|
87 |
+
* WordPress' built-in cookie authentication is always active
|
88 |
+
* for logged in users. However, the API has to check nonces
|
89 |
+
* for each request to ensure users are not vulnerable to CSRF.
|
90 |
+
*
|
91 |
+
* @global mixed $wp_rest_auth_cookie
|
92 |
+
*
|
93 |
+
* @param WP_Error|mixed $result Error from another authentication handler,
|
94 |
+
* null if we should handle it, or another
|
95 |
+
* value if not
|
96 |
+
* @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result,
|
97 |
+
* otherwise true.
|
98 |
+
*/
|
99 |
+
function rest_cookie_check_errors( $result ) {
|
100 |
+
if ( ! empty( $result ) ) {
|
101 |
+
return $result;
|
102 |
+
}
|
103 |
+
|
104 |
+
global $wp_rest_auth_cookie;
|
105 |
+
|
106 |
+
/*
|
107 |
+
* Is cookie authentication being used? (If we get an auth
|
108 |
+
* error, but we're still logged in, another authentication
|
109 |
+
* must have been used.)
|
110 |
+
*/
|
111 |
+
if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
|
112 |
+
return $result;
|
113 |
+
}
|
114 |
+
|
115 |
+
// Is there a nonce?
|
116 |
+
$nonce = null;
|
117 |
+
if ( isset( $_REQUEST['_wp_rest_nonce'] ) ) {
|
118 |
+
$nonce = $_REQUEST['_wp_rest_nonce'];
|
119 |
+
} elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
|
120 |
+
$nonce = $_SERVER['HTTP_X_WP_NONCE'];
|
121 |
+
}
|
122 |
+
|
123 |
+
if ( null === $nonce ) {
|
124 |
+
// No nonce at all, so act as if it's an unauthenticated request.
|
125 |
+
wp_set_current_user( 0 );
|
126 |
+
return true;
|
127 |
+
}
|
128 |
+
|
129 |
+
// Check the nonce.
|
130 |
+
$result = wp_verify_nonce( $nonce, 'wp_rest' );
|
131 |
+
if ( ! $result ) {
|
132 |
+
return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
|
133 |
+
}
|
134 |
+
|
135 |
+
return true;
|
136 |
+
}
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Collect cookie authentication status.
|
140 |
+
*
|
141 |
+
* Collects errors from {@see wp_validate_auth_cookie} for
|
142 |
+
* use by {@see rest_cookie_check_errors}.
|
143 |
+
*
|
144 |
+
* @see current_action()
|
145 |
+
* @global mixed $wp_rest_auth_cookie
|
146 |
+
*/
|
147 |
+
function rest_cookie_collect_status() {
|
148 |
+
global $wp_rest_auth_cookie;
|
149 |
+
|
150 |
+
$status_type = current_action();
|
151 |
+
|
152 |
+
if ( 'auth_cookie_valid' !== $status_type ) {
|
153 |
+
$wp_rest_auth_cookie = substr( $status_type, 12 );
|
154 |
+
return;
|
155 |
+
}
|
156 |
+
|
157 |
+
$wp_rest_auth_cookie = true;
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Retrieve the avatar urls in various sizes based on a given email address.
|
162 |
+
*
|
163 |
+
* {@see get_avatar_url()}
|
164 |
+
*
|
165 |
+
* @param string $email Email address.
|
166 |
+
* @return array $urls Gravatar url for each size.
|
167 |
+
*/
|
168 |
+
function rest_get_avatar_urls( $email ) {
|
169 |
+
$avatar_sizes = rest_get_avatar_sizes();
|
170 |
+
|
171 |
+
$urls = array();
|
172 |
+
foreach ( $avatar_sizes as $size ) {
|
173 |
+
$urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) );
|
174 |
+
}
|
175 |
+
|
176 |
+
return $urls;
|
177 |
+
}
|
178 |
+
|
179 |
+
/**
|
180 |
+
* Return the pixel sizes for avatars.
|
181 |
+
*
|
182 |
+
* @return array
|
183 |
+
*/
|
184 |
+
function rest_get_avatar_sizes() {
|
185 |
+
return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
|
186 |
+
}
|
187 |
+
|
188 |
+
/**
|
189 |
+
* Parse an RFC3339 timestamp into a DateTime.
|
190 |
+
*
|
191 |
+
* @param string $date RFC3339 timestamp.
|
192 |
+
* @param bool $force_utc Force UTC timezone instead of using the timestamp's TZ.
|
193 |
+
* @return DateTime DateTime instance.
|
194 |
+
*/
|
195 |
+
function rest_parse_date( $date, $force_utc = false ) {
|
196 |
+
if ( $force_utc ) {
|
197 |
+
$date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
|
198 |
+
}
|
199 |
+
|
200 |
+
$regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
|
201 |
+
|
202 |
+
if ( ! preg_match( $regex, $date, $matches ) ) {
|
203 |
+
return false;
|
204 |
+
}
|
205 |
+
|
206 |
+
return strtotime( $date );
|
207 |
+
}
|
208 |
+
|
209 |
+
/**
|
210 |
+
* Get a local date with its GMT equivalent, in MySQL datetime format.
|
211 |
+
*
|
212 |
+
* @param string $date RFC3339 timestamp
|
213 |
+
* @param bool $force_utc Whether a UTC timestamp should be forced.
|
214 |
+
* @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
|
215 |
+
* null on failure.
|
216 |
+
*/
|
217 |
+
function rest_get_date_with_gmt( $date, $force_utc = false ) {
|
218 |
+
$date = rest_parse_date( $date, $force_utc );
|
219 |
+
|
220 |
+
if ( empty( $date ) ) {
|
221 |
+
return null;
|
222 |
+
}
|
223 |
+
|
224 |
+
$utc = date( 'Y-m-d H:i:s', $date );
|
225 |
+
$local = get_date_from_gmt( $utc );
|
226 |
+
|
227 |
+
return array( $local, $utc );
|
228 |
+
}
|
229 |
+
|
230 |
+
/**
|
231 |
+
* Parses and formats a MySQL datetime (Y-m-d H:i:s) for ISO8601/RFC3339
|
232 |
+
*
|
233 |
+
* Explicitly strips timezones, as datetimes are not saved with any timezone
|
234 |
+
* information. Including any information on the offset could be misleading.
|
235 |
+
*
|
236 |
+
* @param string $date
|
237 |
+
*/
|
238 |
+
function rest_mysql_to_rfc3339( $date_string ) {
|
239 |
+
$formatted = mysql2date( 'c', $date_string, false );
|
240 |
+
|
241 |
+
// Strip timezone information
|
242 |
+
return preg_replace( '/(?:Z|[+-]\d{2}(?::\d{2})?)$/', '', $formatted );
|
243 |
+
}
|
244 |
+
|
245 |
+
|
246 |
+
/**
|
247 |
+
* Get the timezone object for the site.
|
248 |
+
*
|
249 |
+
* @return DateTimeZone DateTimeZone instance.
|
250 |
+
*/
|
251 |
+
function rest_get_timezone() {
|
252 |
+
static $zone = null;
|
253 |
+
|
254 |
+
if ( null !== $zone ) {
|
255 |
+
return $zone;
|
256 |
+
}
|
257 |
+
|
258 |
+
$tzstring = get_option( 'timezone_string' );
|
259 |
+
|
260 |
+
if ( ! $tzstring ) {
|
261 |
+
// Create a UTC+- zone if no timezone string exists
|
262 |
+
$current_offset = get_option( 'gmt_offset' );
|
263 |
+
if ( 0 === $current_offset ) {
|
264 |
+
$tzstring = 'UTC';
|
265 |
+
} elseif ( $current_offset < 0 ) {
|
266 |
+
$tzstring = 'Etc/GMT' . $current_offset;
|
267 |
+
} else {
|
268 |
+
$tzstring = 'Etc/GMT+' . $current_offset;
|
269 |
+
}
|
270 |
+
}
|
271 |
+
$zone = new DateTimeZone( $tzstring );
|
272 |
+
|
273 |
+
return $zone;
|
274 |
+
}
|
275 |
+
|
276 |
+
/**
|
277 |
+
* Retrieve the avatar url for a user who provided a user ID or email address.
|
278 |
+
*
|
279 |
+
* @deprecated WPAPI-2.0
|
280 |
+
* {@see get_avatar()} doesn't return just the URL, so we have to
|
281 |
+
* extract it here.
|
282 |
+
*
|
283 |
+
* @param string $email Email address.
|
284 |
+
* @return string URL for the user's avatar, empty string otherwise.
|
285 |
+
*/
|
286 |
+
function rest_get_avatar_url( $email ) {
|
287 |
+
_deprecated_function( 'rest_get_avatar_url', 'WPAPI-2.0', 'rest_get_avatar_urls' );
|
288 |
+
/**
|
289 |
+
* Use the WP Core `get_avatar_url()` function introduced in 4.2.
|
290 |
+
*/
|
291 |
+
if ( function_exists( 'get_avatar_url' ) ) {
|
292 |
+
return esc_url_raw( get_avatar_url( $email ) );
|
293 |
+
}
|
294 |
+
$avatar_html = get_avatar( $email );
|
295 |
+
|
296 |
+
// Strip the avatar url from the get_avatar img tag.
|
297 |
+
preg_match( '/src=["|\'](.+)[\&|"|\']/U', $avatar_html, $matches );
|
298 |
+
|
299 |
+
if ( isset( $matches[1] ) && ! empty( $matches[1] ) ) {
|
300 |
+
return esc_url_raw( $matches[1] );
|
301 |
+
}
|
302 |
+
|
303 |
+
return '';
|
304 |
+
}
|
lib/endpoints/class-wp-rest-attachments-controller.php
ADDED
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Create a single attachment
|
7 |
+
*
|
8 |
+
* @param WP_REST_Request $request Full details about the request
|
9 |
+
* @return WP_Error|WP_REST_Response
|
10 |
+
*/
|
11 |
+
public function create_item( $request ) {
|
12 |
+
|
13 |
+
// Permissions check - Note: "upload_files" cap is returned for an attachment by $post_type_obj->cap->create_posts
|
14 |
+
$post_type_obj = get_post_type_object( $this->post_type );
|
15 |
+
if ( ! current_user_can( $post_type_obj->cap->create_posts ) || ! current_user_can( $post_type_obj->cap->edit_posts ) ) {
|
16 |
+
return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to post on this site.' ), array( 'status' => 400 ) );
|
17 |
+
}
|
18 |
+
|
19 |
+
// If a user is trying to attach to a post make sure they have permissions. Bail early if post_id is not being passed
|
20 |
+
if ( ! empty( $request['post'] ) ) {
|
21 |
+
$parent = get_post( (int) $request['post'] );
|
22 |
+
$post_parent_type = get_post_type_object( $parent->post_type );
|
23 |
+
if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
|
24 |
+
return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => 401 ) );
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
// Get the file via $_FILES or raw data
|
29 |
+
$files = $request->get_file_params();
|
30 |
+
$headers = $request->get_headers();
|
31 |
+
if ( ! empty( $files ) ) {
|
32 |
+
$file = $this->upload_from_file( $files, $headers );
|
33 |
+
} else {
|
34 |
+
$file = $this->upload_from_data( $request->get_body(), $headers );
|
35 |
+
}
|
36 |
+
|
37 |
+
if ( is_wp_error( $file ) ) {
|
38 |
+
return $file;
|
39 |
+
}
|
40 |
+
|
41 |
+
$name = basename( $file['file'] );
|
42 |
+
$name_parts = pathinfo( $name );
|
43 |
+
$name = trim( substr( $name, 0, -(1 + strlen( $name_parts['extension'] ) ) ) );
|
44 |
+
|
45 |
+
$url = $file['url'];
|
46 |
+
$type = $file['type'];
|
47 |
+
$file = $file['file'];
|
48 |
+
$title = $name;
|
49 |
+
$caption = '';
|
50 |
+
|
51 |
+
// use image exif/iptc data for title and caption defaults if possible
|
52 |
+
// @codingStandardsIgnoreStart
|
53 |
+
$image_meta = @wp_read_image_metadata( $file );
|
54 |
+
// @codingStandardsIgnoreEnd
|
55 |
+
if ( ! empty( $image_meta ) ) {
|
56 |
+
if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
|
57 |
+
$title = $image_meta['title'];
|
58 |
+
}
|
59 |
+
|
60 |
+
if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
|
61 |
+
$caption = $image_meta['caption'];
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
$attachment = $this->prepare_item_for_database( $request );
|
66 |
+
$attachment->file = $file;
|
67 |
+
$attachment->post_mime_type = $type;
|
68 |
+
$attachment->guid = $url;
|
69 |
+
$id = wp_insert_post( $attachment, true );
|
70 |
+
if ( is_wp_error( $id ) ) {
|
71 |
+
return $id;
|
72 |
+
}
|
73 |
+
|
74 |
+
wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
|
75 |
+
|
76 |
+
$this->update_additional_fields_for_object( $attachment, $request );
|
77 |
+
|
78 |
+
$response = $this->get_item( array(
|
79 |
+
'id' => $id,
|
80 |
+
'context' => 'edit',
|
81 |
+
) );
|
82 |
+
$response = rest_ensure_response( $response );
|
83 |
+
$response->set_status( 201 );
|
84 |
+
$response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $attachment->post_type ) . '/' . $id ) );
|
85 |
+
|
86 |
+
return $response;
|
87 |
+
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Update a single post
|
92 |
+
*
|
93 |
+
* @param WP_REST_Request $request Full details about the request
|
94 |
+
* @return WP_Error|WP_REST_Response
|
95 |
+
*/
|
96 |
+
public function update_item( $request ) {
|
97 |
+
$response = parent::update_item( $request );
|
98 |
+
if ( is_wp_error( $response ) ) {
|
99 |
+
return $response;
|
100 |
+
}
|
101 |
+
|
102 |
+
$response = rest_ensure_response( $response );
|
103 |
+
$data = $response->get_data();
|
104 |
+
|
105 |
+
if ( isset( $request['alt_text'] ) ) {
|
106 |
+
update_post_meta( $data['id'], '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
|
107 |
+
}
|
108 |
+
|
109 |
+
$response = $this->get_item( array(
|
110 |
+
'id' => $data['id'],
|
111 |
+
'context' => 'edit',
|
112 |
+
));
|
113 |
+
$response = rest_ensure_response( $response );
|
114 |
+
$response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $this->post_type ) . '/' . $data['id'] ) );
|
115 |
+
return $response;
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Prepare a single attachment for create or update
|
120 |
+
*
|
121 |
+
* @param WP_REST_Request $request Request object
|
122 |
+
* @return WP_Error|obj $prepared_attachment Post object
|
123 |
+
*/
|
124 |
+
protected function prepare_item_for_database( $request ) {
|
125 |
+
$prepared_attachment = parent::prepare_item_for_database( $request );
|
126 |
+
|
127 |
+
if ( isset( $request['caption'] ) ) {
|
128 |
+
$prepared_attachment->post_excerpt = wp_filter_post_kses( $request['caption'] );
|
129 |
+
}
|
130 |
+
|
131 |
+
if ( isset( $request['description'] ) ) {
|
132 |
+
$prepared_attachment->post_content = wp_filter_post_kses( $request['description'] );
|
133 |
+
}
|
134 |
+
|
135 |
+
if ( isset( $request['post'] ) ) {
|
136 |
+
$prepared_attachment->post_parent = (int) $request['post_parent'];
|
137 |
+
}
|
138 |
+
|
139 |
+
return $prepared_attachment;
|
140 |
+
}
|
141 |
+
|
142 |
+
/**
|
143 |
+
* Prepare a single attachment output for response
|
144 |
+
*
|
145 |
+
* @param WP_Post $post Post object
|
146 |
+
* @param WP_REST_Request $request Request object
|
147 |
+
* @return array $response
|
148 |
+
*/
|
149 |
+
public function prepare_item_for_response( $post, $request ) {
|
150 |
+
$response = parent::prepare_item_for_response( $post, $request );
|
151 |
+
$data = $response->get_data();
|
152 |
+
|
153 |
+
$data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true );
|
154 |
+
$data['caption'] = $post->post_excerpt;
|
155 |
+
$data['description'] = $post->post_content;
|
156 |
+
$data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file';
|
157 |
+
$data['media_details'] = wp_get_attachment_metadata( $post->ID );
|
158 |
+
$data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
|
159 |
+
$data['source_url'] = wp_get_attachment_url( $post->ID );
|
160 |
+
|
161 |
+
// Ensure empty details is an empty object
|
162 |
+
if ( empty( $data['media_details'] ) ) {
|
163 |
+
$data['media_details'] = new stdClass;
|
164 |
+
} elseif ( ! empty( $data['media_details']['sizes'] ) ) {
|
165 |
+
$img_url_basename = wp_basename( $data['source_url'] );
|
166 |
+
|
167 |
+
foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {
|
168 |
+
// Use the same method image_downsize() does
|
169 |
+
$size_data['source_url'] = str_replace( $img_url_basename, $size_data['file'], $data['source_url'] );
|
170 |
+
}
|
171 |
+
} else {
|
172 |
+
$data['media_details']['sizes'] = new stdClass;
|
173 |
+
}
|
174 |
+
|
175 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
176 |
+
|
177 |
+
$data = $this->filter_response_by_context( $data, $context );
|
178 |
+
|
179 |
+
// Wrap the data in a response object
|
180 |
+
$data = rest_ensure_response( $data );
|
181 |
+
|
182 |
+
$data->add_links( $this->prepare_links( $post ) );
|
183 |
+
|
184 |
+
return apply_filters( 'rest_prepare_attachment', $data, $post, $request );
|
185 |
+
}
|
186 |
+
|
187 |
+
/**
|
188 |
+
* Get the Attachment's schema, conforming to JSON Schema
|
189 |
+
*
|
190 |
+
* @return array
|
191 |
+
*/
|
192 |
+
public function get_item_schema() {
|
193 |
+
|
194 |
+
$schema = parent::get_item_schema();
|
195 |
+
|
196 |
+
$schema['properties']['alt_text'] = array(
|
197 |
+
'description' => 'Alternative text to display when attachment is not displayed.',
|
198 |
+
'type' => 'string',
|
199 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
200 |
+
);
|
201 |
+
$schema['properties']['caption'] = array(
|
202 |
+
'description' => 'The caption for the attachment.',
|
203 |
+
'type' => 'string',
|
204 |
+
'context' => array( 'view', 'edit' ),
|
205 |
+
);
|
206 |
+
$schema['properties']['description'] = array(
|
207 |
+
'description' => 'The description for the attachment.',
|
208 |
+
'type' => 'string',
|
209 |
+
'context' => array( 'view', 'edit' ),
|
210 |
+
);
|
211 |
+
$schema['properties']['media_type'] = array(
|
212 |
+
'description' => 'Type of attachment.',
|
213 |
+
'type' => 'string',
|
214 |
+
'enum' => array( 'image', 'file' ),
|
215 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
216 |
+
'readonly' => true,
|
217 |
+
);
|
218 |
+
$schema['properties']['media_details'] = array(
|
219 |
+
'description' => 'Details about the attachment file, specific to its type.',
|
220 |
+
'type' => 'object',
|
221 |
+
'context' => array( 'view', 'edit' ),
|
222 |
+
'readonly' => true,
|
223 |
+
);
|
224 |
+
$schema['properties']['post'] = array(
|
225 |
+
'description' => 'The ID for the associated post of the attachment.',
|
226 |
+
'type' => 'integer',
|
227 |
+
'context' => array( 'view', 'edit' ),
|
228 |
+
);
|
229 |
+
$schema['properties']['source_url'] = array(
|
230 |
+
'description' => 'URL to the original attachment file.',
|
231 |
+
'type' => 'string',
|
232 |
+
'format' => 'uri',
|
233 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
234 |
+
'readonly' => true,
|
235 |
+
);
|
236 |
+
return $schema;
|
237 |
+
}
|
238 |
+
|
239 |
+
/**
|
240 |
+
* Handle an upload via raw POST data
|
241 |
+
*
|
242 |
+
* @param array $data Supplied file data
|
243 |
+
* @param array $headers HTTP headers from the request
|
244 |
+
* @return array|WP_Error Data from {@see wp_handle_sideload()}
|
245 |
+
*/
|
246 |
+
protected function upload_from_data( $data, $headers ) {
|
247 |
+
if ( empty( $data ) ) {
|
248 |
+
return new WP_Error( 'rest_upload_no_data', __( 'No data supplied' ), array( 'status' => 400 ) );
|
249 |
+
}
|
250 |
+
|
251 |
+
if ( empty( $headers['content_type'] ) ) {
|
252 |
+
return new WP_Error( 'rest_upload_no_content_type', __( 'No Content-Type supplied' ), array( 'status' => 400 ) );
|
253 |
+
}
|
254 |
+
|
255 |
+
if ( empty( $headers['content_disposition'] ) ) {
|
256 |
+
return new WP_Error( 'rest_upload_no_content_disposition', __( 'No Content-Disposition supplied' ), array( 'status' => 400 ) );
|
257 |
+
}
|
258 |
+
|
259 |
+
// Get the filename
|
260 |
+
$filename = null;
|
261 |
+
|
262 |
+
foreach ( $headers['content_disposition'] as $part ) {
|
263 |
+
$part = trim( $part );
|
264 |
+
|
265 |
+
if ( strpos( $part, 'filename' ) !== 0 ) {
|
266 |
+
continue;
|
267 |
+
}
|
268 |
+
|
269 |
+
$filenameparts = explode( '=', $part );
|
270 |
+
$filename = trim( $filenameparts[1] );
|
271 |
+
}
|
272 |
+
|
273 |
+
if ( empty( $filename ) ) {
|
274 |
+
return new WP_Error( 'rest_upload_invalid_disposition', __( 'Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as "filename=image.png" or similar.' ), array( 'status' => 400 ) );
|
275 |
+
}
|
276 |
+
|
277 |
+
if ( ! empty( $headers['content_md5'] ) ) {
|
278 |
+
$content_md5 = array_shift( $headers['content_md5'] );
|
279 |
+
$expected = trim( $content_md5 );
|
280 |
+
$actual = md5( $data );
|
281 |
+
|
282 |
+
if ( $expected !== $actual ) {
|
283 |
+
return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected' ), array( 'status' => 412 ) );
|
284 |
+
}
|
285 |
+
}
|
286 |
+
|
287 |
+
// Get the content-type
|
288 |
+
$type = array_shift( $headers['content_type'] );
|
289 |
+
|
290 |
+
// Save the file
|
291 |
+
$tmpfname = wp_tempnam( $filename );
|
292 |
+
|
293 |
+
$fp = fopen( $tmpfname, 'w+' );
|
294 |
+
|
295 |
+
if ( ! $fp ) {
|
296 |
+
return new WP_Error( 'rest_upload_file_error', __( 'Could not open file handle' ), array( 'status' => 500 ) );
|
297 |
+
}
|
298 |
+
|
299 |
+
fwrite( $fp, $data );
|
300 |
+
fclose( $fp );
|
301 |
+
|
302 |
+
// Now, sideload it in
|
303 |
+
$file_data = array(
|
304 |
+
'error' => null,
|
305 |
+
'tmp_name' => $tmpfname,
|
306 |
+
'name' => $filename,
|
307 |
+
'type' => $type,
|
308 |
+
);
|
309 |
+
$overrides = array(
|
310 |
+
'test_form' => false,
|
311 |
+
);
|
312 |
+
$sideloaded = wp_handle_sideload( $file_data, $overrides );
|
313 |
+
|
314 |
+
if ( isset( $sideloaded['error'] ) ) {
|
315 |
+
// @codingStandardsIgnoreStart
|
316 |
+
@unlink( $tmpfname );
|
317 |
+
// @codingStandardsIgnoreEnd
|
318 |
+
return new WP_Error( 'rest_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) );
|
319 |
+
}
|
320 |
+
|
321 |
+
return $sideloaded;
|
322 |
+
}
|
323 |
+
|
324 |
+
/**
|
325 |
+
* Handle an upload via multipart/form-data ($_FILES)
|
326 |
+
*
|
327 |
+
* @param array $files Data from $_FILES
|
328 |
+
* @param array $headers HTTP headers from the request
|
329 |
+
* @return array|WP_Error Data from {@see wp_handle_upload()}
|
330 |
+
*/
|
331 |
+
protected function upload_from_file( $files, $headers ) {
|
332 |
+
if ( empty( $files ) ) {
|
333 |
+
return new WP_Error( 'rest_upload_no_data', __( 'No data supplied' ), array( 'status' => 400 ) );
|
334 |
+
}
|
335 |
+
|
336 |
+
// Verify hash, if given
|
337 |
+
if ( ! empty( $headers['CONTENT_MD5'] ) ) {
|
338 |
+
$expected = trim( $headers['CONTENT_MD5'] );
|
339 |
+
$actual = md5_file( $files['file']['tmp_name'] );
|
340 |
+
if ( $expected !== $actual ) {
|
341 |
+
return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected' ), array( 'status' => 412 ) );
|
342 |
+
}
|
343 |
+
}
|
344 |
+
|
345 |
+
// Pass off to WP to handle the actual upload
|
346 |
+
$overrides = array(
|
347 |
+
'test_form' => false,
|
348 |
+
);
|
349 |
+
// Bypasses is_uploaded_file() when running unit tests
|
350 |
+
if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) {
|
351 |
+
$overrides['action'] = 'wp_handle_mock_upload';
|
352 |
+
}
|
353 |
+
|
354 |
+
$file = wp_handle_upload( $files, $overrides );
|
355 |
+
|
356 |
+
if ( isset( $file['error'] ) ) {
|
357 |
+
return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) );
|
358 |
+
}
|
359 |
+
|
360 |
+
return $file;
|
361 |
+
}
|
362 |
+
|
363 |
+
}
|
lib/endpoints/class-wp-rest-comments-controller.php
ADDED
@@ -0,0 +1,1069 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Access comments
|
5 |
+
*/
|
6 |
+
class WP_REST_Comments_Controller extends WP_REST_Controller {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Register the routes for the objects of the controller.
|
10 |
+
*/
|
11 |
+
public function register_routes() {
|
12 |
+
|
13 |
+
$query_params = $this->get_collection_params();
|
14 |
+
register_rest_route( 'wp/v2', '/comments', array(
|
15 |
+
array(
|
16 |
+
'methods' => WP_REST_Server::READABLE,
|
17 |
+
'callback' => array( $this, 'get_items' ),
|
18 |
+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
19 |
+
'args' => $query_params,
|
20 |
+
),
|
21 |
+
array(
|
22 |
+
'methods' => WP_REST_Server::CREATABLE,
|
23 |
+
'callback' => array( $this, 'create_item' ),
|
24 |
+
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
25 |
+
'args' => array(
|
26 |
+
'post' => array(
|
27 |
+
'required' => true,
|
28 |
+
'sanitize_callback' => 'absint',
|
29 |
+
),
|
30 |
+
'type' => array(
|
31 |
+
'default' => '',
|
32 |
+
'sanitize_callback' => 'sanitize_key',
|
33 |
+
),
|
34 |
+
'parent' => array(
|
35 |
+
'default' => 0,
|
36 |
+
'sanitize_callback' => 'absint',
|
37 |
+
),
|
38 |
+
'content' => array(
|
39 |
+
'default' => '',
|
40 |
+
'sanitize_callback' => 'wp_filter_post_kses',
|
41 |
+
),
|
42 |
+
'author' => array(
|
43 |
+
'default' => 0,
|
44 |
+
'sanitize_callback' => 'absint',
|
45 |
+
),
|
46 |
+
'author_name' => array(
|
47 |
+
'default' => '',
|
48 |
+
'sanitize_callback' => 'sanitize_text_field',
|
49 |
+
),
|
50 |
+
'author_email' => array(
|
51 |
+
'default' => '',
|
52 |
+
'sanitize_callback' => 'sanitize_email',
|
53 |
+
),
|
54 |
+
'author_url' => array(
|
55 |
+
'default' => '',
|
56 |
+
'sanitize_callback' => 'esc_url_raw',
|
57 |
+
),
|
58 |
+
'karma' => array(
|
59 |
+
'default' => 0,
|
60 |
+
'sanitize_callback' => 'absint',
|
61 |
+
),
|
62 |
+
'status' => array(
|
63 |
+
'sanitize_callback' => 'sanitize_key',
|
64 |
+
),
|
65 |
+
'date' => array(
|
66 |
+
'default' => current_time( 'mysql' ),
|
67 |
+
),
|
68 |
+
'date_gmt' => array(
|
69 |
+
'default' => current_time( 'mysql', true ),
|
70 |
+
),
|
71 |
+
),
|
72 |
+
),
|
73 |
+
) );
|
74 |
+
|
75 |
+
register_rest_route( 'wp/v2', '/comments/(?P<id>[\d]+)', array(
|
76 |
+
array(
|
77 |
+
'methods' => WP_REST_Server::READABLE,
|
78 |
+
'callback' => array( $this, 'get_item' ),
|
79 |
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
80 |
+
'args' => array(
|
81 |
+
'context' => array(
|
82 |
+
'default' => 'view',
|
83 |
+
),
|
84 |
+
),
|
85 |
+
),
|
86 |
+
array(
|
87 |
+
'methods' => WP_REST_Server::EDITABLE,
|
88 |
+
'callback' => array( $this, 'update_item' ),
|
89 |
+
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
90 |
+
'args' => array(
|
91 |
+
'post' => array(
|
92 |
+
'sanitize_callback' => 'absint',
|
93 |
+
),
|
94 |
+
'type' => array(
|
95 |
+
'sanitize_callback' => 'sanitize_key',
|
96 |
+
),
|
97 |
+
'parent' => array(
|
98 |
+
'sanitize_callback' => 'absint',
|
99 |
+
),
|
100 |
+
'content' => array(
|
101 |
+
'sanitize_callback' => 'wp_filter_post_kses',
|
102 |
+
),
|
103 |
+
'author' => array(
|
104 |
+
'sanitize_callback' => 'absint',
|
105 |
+
),
|
106 |
+
'author_name' => array(
|
107 |
+
'sanitize_callback' => 'sanitize_text_field',
|
108 |
+
),
|
109 |
+
'author_email' => array(
|
110 |
+
'sanitize_callback' => 'sanitize_email',
|
111 |
+
),
|
112 |
+
'author_url' => array(
|
113 |
+
'sanitize_callback' => 'esc_url_raw',
|
114 |
+
),
|
115 |
+
'karma' => array(
|
116 |
+
'sanitize_callback' => 'absint',
|
117 |
+
),
|
118 |
+
'status' => array(
|
119 |
+
'sanitize_callback' => 'sanitize_key',
|
120 |
+
),
|
121 |
+
'date' => array(),
|
122 |
+
'date_gmt' => array(),
|
123 |
+
),
|
124 |
+
),
|
125 |
+
array(
|
126 |
+
'methods' => WP_REST_Server::DELETABLE,
|
127 |
+
'callback' => array( $this, 'delete_item' ),
|
128 |
+
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
129 |
+
'args' => array(
|
130 |
+
'force' => array(),
|
131 |
+
),
|
132 |
+
),
|
133 |
+
) );
|
134 |
+
|
135 |
+
register_rest_route( 'wp/v2', '/comments/schema', array(
|
136 |
+
'methods' => WP_REST_Server::READABLE,
|
137 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
138 |
+
) );
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Get a list of comments.
|
143 |
+
*
|
144 |
+
* @param WP_REST_Request $request Full details about the request.
|
145 |
+
* @return WP_Error|WP_REST_Response
|
146 |
+
*/
|
147 |
+
public function get_items( $request ) {
|
148 |
+
$prepared_args = $this->prepare_items_query( $request );
|
149 |
+
|
150 |
+
$query = new WP_Comment_Query;
|
151 |
+
$query_result = $query->query( $prepared_args );
|
152 |
+
|
153 |
+
$comments = array();
|
154 |
+
foreach ( $query_result as $comment ) {
|
155 |
+
$post = get_post( $comment->comment_post_ID );
|
156 |
+
if ( ! $this->check_read_post_permission( $post ) || ! $this->check_read_permission( $comment ) ) {
|
157 |
+
|
158 |
+
continue;
|
159 |
+
}
|
160 |
+
|
161 |
+
$data = $this->prepare_item_for_response( $comment, $request );
|
162 |
+
$comments[] = $this->prepare_response_for_collection( $data );
|
163 |
+
}
|
164 |
+
|
165 |
+
$response = rest_ensure_response( $comments );
|
166 |
+
unset( $prepared_args['number'] );
|
167 |
+
unset( $prepared_args['offset'] );
|
168 |
+
$query = new WP_Comment_Query;
|
169 |
+
$prepared_args['count'] = true;
|
170 |
+
$total_comments = $query->query( $prepared_args );
|
171 |
+
$response->header( 'X-WP-Total', (int) $total_comments );
|
172 |
+
$max_pages = ceil( $total_comments / $request['per_page'] );
|
173 |
+
$response->header( 'X-WP-TotalPages', (int) $max_pages );
|
174 |
+
|
175 |
+
$base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/comments' ) );
|
176 |
+
if ( $request['page'] > 1 ) {
|
177 |
+
$prev_page = $request['page'] - 1;
|
178 |
+
if ( $prev_page > $max_pages ) {
|
179 |
+
$prev_page = $max_pages;
|
180 |
+
}
|
181 |
+
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
182 |
+
$response->link_header( 'prev', $prev_link );
|
183 |
+
}
|
184 |
+
if ( $max_pages > $request['page'] ) {
|
185 |
+
$next_page = $request['page'] + 1;
|
186 |
+
$next_link = add_query_arg( 'page', $next_page, $base );
|
187 |
+
$response->link_header( 'next', $next_link );
|
188 |
+
}
|
189 |
+
|
190 |
+
return $response;
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Get a comment.
|
195 |
+
*
|
196 |
+
* @param WP_REST_Request $request Full details about the request.
|
197 |
+
* @return WP_Error|WP_REST_Response
|
198 |
+
*/
|
199 |
+
public function get_item( $request ) {
|
200 |
+
$id = (int) $request['id'];
|
201 |
+
|
202 |
+
$comment = get_comment( $id );
|
203 |
+
if ( empty( $comment ) ) {
|
204 |
+
return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) );
|
205 |
+
}
|
206 |
+
|
207 |
+
$post = get_post( $comment->comment_post_ID );
|
208 |
+
if ( empty( $post ) ) {
|
209 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
|
210 |
+
}
|
211 |
+
|
212 |
+
$data = $this->prepare_item_for_response( $comment, $request );
|
213 |
+
$response = rest_ensure_response( $data );
|
214 |
+
|
215 |
+
return $response;
|
216 |
+
}
|
217 |
+
|
218 |
+
/**
|
219 |
+
* Create a comment.
|
220 |
+
*
|
221 |
+
* @param WP_REST_Request $request Full details about the request.
|
222 |
+
* @return WP_Error|WP_REST_Response
|
223 |
+
*/
|
224 |
+
public function create_item( $request ) {
|
225 |
+
if ( ! empty( $request['id'] ) ) {
|
226 |
+
return new WP_Error( 'rest_comment_exists', __( 'Cannot create existing comment.' ), array( 'status' => 400 ) );
|
227 |
+
}
|
228 |
+
|
229 |
+
$post = get_post( $request['post'] );
|
230 |
+
if ( empty( $post ) ) {
|
231 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
|
232 |
+
}
|
233 |
+
|
234 |
+
$prepared_comment = $this->prepare_item_for_database( $request );
|
235 |
+
// Setting remaining values before wp_insert_comment so we can
|
236 |
+
// use wp_allow_comment().
|
237 |
+
$prepared_comment['comment_author_IP'] = '127.0.0.1';
|
238 |
+
$prepared_comment['comment_agent'] = '';
|
239 |
+
$prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment );
|
240 |
+
|
241 |
+
$prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request );
|
242 |
+
|
243 |
+
$comment_id = wp_insert_comment( $prepared_comment );
|
244 |
+
if ( ! $comment_id ) {
|
245 |
+
return new WP_Error( 'rest_comment_failed_create', __( 'Creating comment failed.' ), array( 'status' => 500 ) );
|
246 |
+
}
|
247 |
+
|
248 |
+
if ( isset( $request['status'] ) ) {
|
249 |
+
$comment = get_comment( $comment_id );
|
250 |
+
$this->handle_status_param( $request['status'], $comment );
|
251 |
+
}
|
252 |
+
|
253 |
+
$this->update_additional_fields_for_object( get_comment( $comment_id ), $request );
|
254 |
+
|
255 |
+
$context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view';
|
256 |
+
$response = $this->get_item( array(
|
257 |
+
'id' => $comment_id,
|
258 |
+
'context' => $context,
|
259 |
+
) );
|
260 |
+
$response = rest_ensure_response( $response );
|
261 |
+
if ( is_wp_error( $response ) ) {
|
262 |
+
return $response;
|
263 |
+
}
|
264 |
+
$response->set_status( 201 );
|
265 |
+
$response->header( 'Location', rest_url( '/wp/v2/comments/' . $comment_id ) );
|
266 |
+
|
267 |
+
return $response;
|
268 |
+
}
|
269 |
+
|
270 |
+
/**
|
271 |
+
* Edit a comment
|
272 |
+
*
|
273 |
+
* @param WP_REST_Request $request Full details about the request.
|
274 |
+
* @return WP_Error|WP_REST_Response
|
275 |
+
*/
|
276 |
+
public function update_item( $request ) {
|
277 |
+
$id = (int) $request['id'];
|
278 |
+
|
279 |
+
$comment = get_comment( $id );
|
280 |
+
if ( empty( $comment ) ) {
|
281 |
+
return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) );
|
282 |
+
}
|
283 |
+
|
284 |
+
if ( isset( $request['type'] ) && $request['type'] !== $comment->comment_type ) {
|
285 |
+
return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you cannot change the comment type.' ), array( 'status' => 404 ) );
|
286 |
+
}
|
287 |
+
|
288 |
+
$prepared_args = $this->prepare_item_for_database( $request );
|
289 |
+
|
290 |
+
if ( empty( $prepared_args ) && isset( $request['status'] ) ) {
|
291 |
+
// Only the comment status is being changed.
|
292 |
+
$change = $this->handle_status_param( $request['status'], $comment );
|
293 |
+
if ( ! $change ) {
|
294 |
+
return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment status failed.' ), array( 'status' => 500 ) );
|
295 |
+
}
|
296 |
+
} else {
|
297 |
+
$prepared_args['comment_ID'] = $id;
|
298 |
+
|
299 |
+
$updated = wp_update_comment( $prepared_args );
|
300 |
+
if ( 0 === $updated ) {
|
301 |
+
return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment failed.' ), array( 'status' => 500 ) );
|
302 |
+
}
|
303 |
+
|
304 |
+
if ( isset( $request['status'] ) ) {
|
305 |
+
$this->handle_status_param( $request['status'], $comment );
|
306 |
+
}
|
307 |
+
}
|
308 |
+
|
309 |
+
$this->update_additional_fields_for_object( get_comment( $id ), $request );
|
310 |
+
|
311 |
+
$response = $this->get_item( array(
|
312 |
+
'id' => $id,
|
313 |
+
'context' => 'edit',
|
314 |
+
) );
|
315 |
+
$response = rest_ensure_response( $response );
|
316 |
+
if ( is_wp_error( $response ) ) {
|
317 |
+
return $response;
|
318 |
+
}
|
319 |
+
$response->header( 'Location', rest_url( '/wp/v2/comments/' . $comment->comment_ID ) );
|
320 |
+
|
321 |
+
return $response;
|
322 |
+
}
|
323 |
+
|
324 |
+
/**
|
325 |
+
* Delete a comment.
|
326 |
+
*
|
327 |
+
* @param WP_REST_Request $request Full details about the request.
|
328 |
+
* @return WP_Error|array
|
329 |
+
*/
|
330 |
+
public function delete_item( $request ) {
|
331 |
+
$id = (int) $request['id'];
|
332 |
+
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
|
333 |
+
|
334 |
+
$comment = get_comment( $id );
|
335 |
+
if ( empty( $comment ) ) {
|
336 |
+
return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) );
|
337 |
+
}
|
338 |
+
|
339 |
+
/**
|
340 |
+
* Filter whether the comment type supports trashing.
|
341 |
+
*
|
342 |
+
* @param boolean $supports_trash Does the comment type support trashing?
|
343 |
+
* @param stdClass $comment Comment we're attempting to trash.
|
344 |
+
*/
|
345 |
+
$supports_trash = apply_filters( 'rest_comment_type_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment );
|
346 |
+
|
347 |
+
$get_request = new WP_REST_Request( 'GET', rest_url( '/wp/v2/comments/' . $id ) );
|
348 |
+
$get_request->set_param( 'context', 'edit' );
|
349 |
+
$response = $this->prepare_item_for_response( $comment, $get_request );
|
350 |
+
|
351 |
+
if ( $force ) {
|
352 |
+
$result = wp_delete_comment( $comment->comment_ID, true );
|
353 |
+
} else {
|
354 |
+
// If we don't support trashing for this type, error out
|
355 |
+
if ( ! $supports_trash ) {
|
356 |
+
return new WP_Error( 'rest_trash_not_supported', __( 'The comment does not support trashing.' ), array( 'status' => 501 ) );
|
357 |
+
}
|
358 |
+
|
359 |
+
$result = wp_trash_comment( $comment->comment_ID );
|
360 |
+
}
|
361 |
+
|
362 |
+
if ( ! $result ) {
|
363 |
+
return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) );
|
364 |
+
}
|
365 |
+
|
366 |
+
return $response;
|
367 |
+
}
|
368 |
+
|
369 |
+
|
370 |
+
/**
|
371 |
+
* Check if a given request has access to read comments
|
372 |
+
*
|
373 |
+
* @param WP_REST_Request $request Full details about the request.
|
374 |
+
* @return bool|WP_Error
|
375 |
+
*/
|
376 |
+
public function get_items_permissions_check( $request ) {
|
377 |
+
|
378 |
+
// If the post id is specified, check that we can read the post
|
379 |
+
if ( isset( $request['post'] ) ) {
|
380 |
+
$post = get_post( (int) $request['post'] );
|
381 |
+
|
382 |
+
if ( $post && ! $this->check_read_post_permission( $post ) ) {
|
383 |
+
return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ) );
|
384 |
+
}
|
385 |
+
}
|
386 |
+
|
387 |
+
if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'manage_comments' ) ) {
|
388 |
+
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => 403 ) );
|
389 |
+
}
|
390 |
+
|
391 |
+
return true;
|
392 |
+
}
|
393 |
+
|
394 |
+
/**
|
395 |
+
* Check if a given request has access to read the comment
|
396 |
+
*
|
397 |
+
* @param WP_REST_Request $request Full details about the request.
|
398 |
+
* @return bool|WP_Error
|
399 |
+
*/
|
400 |
+
public function get_item_permissions_check( $request ) {
|
401 |
+
$id = (int) $request['id'];
|
402 |
+
|
403 |
+
$comment = get_comment( $id );
|
404 |
+
|
405 |
+
if ( ! $comment ) {
|
406 |
+
return true;
|
407 |
+
}
|
408 |
+
|
409 |
+
if ( ! $this->check_read_permission( $comment ) ) {
|
410 |
+
return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read this comment.' ), array( 'status' => 403 ) );
|
411 |
+
}
|
412 |
+
|
413 |
+
$post = get_post( $comment->comment_post_ID );
|
414 |
+
|
415 |
+
if ( $post && ! $this->check_read_post_permission( $post ) ) {
|
416 |
+
return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => 403 ) );
|
417 |
+
}
|
418 |
+
|
419 |
+
if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
|
420 |
+
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this comment with edit context.' ), array( 'status' => 403 ) );
|
421 |
+
}
|
422 |
+
|
423 |
+
return true;
|
424 |
+
}
|
425 |
+
|
426 |
+
/**
|
427 |
+
* Check if a given request has access to create a comment
|
428 |
+
*
|
429 |
+
* @param WP_REST_Request $request Full details about the request.
|
430 |
+
* @return bool|WP_Error
|
431 |
+
*/
|
432 |
+
public function create_item_permissions_check( $request ) {
|
433 |
+
|
434 |
+
// Limit who can set comment `author`, `karma` or `status` to anything other than the default.
|
435 |
+
if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
|
436 |
+
return new WP_Error( 'rest_comment_invalid_author', __( 'Comment author invalid.' ), array( 'status' => 403 ) );
|
437 |
+
}
|
438 |
+
if ( isset( $request['karma'] ) && $request['karma'] > 0 && ! current_user_can( 'moderate_comments' ) ) {
|
439 |
+
return new WP_Error( 'rest_comment_invalid_karma', __( 'Sorry, you cannot set karma for comments.' ), array( 'status' => 403 ) );
|
440 |
+
}
|
441 |
+
if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
|
442 |
+
return new WP_Error( 'rest_comment_invalid_status', __( 'Sorry, you cannot set status for comments.' ), array( 'status' => 403 ) );
|
443 |
+
}
|
444 |
+
|
445 |
+
// If the post id isn't specified, presume we can create.
|
446 |
+
if ( ! isset( $request['post'] ) ) {
|
447 |
+
return true;
|
448 |
+
}
|
449 |
+
|
450 |
+
$post = get_post( (int) $request['post'] );
|
451 |
+
|
452 |
+
if ( $post ) {
|
453 |
+
|
454 |
+
if ( ! $this->check_read_post_permission( $post ) ) {
|
455 |
+
return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => 403 ) );
|
456 |
+
}
|
457 |
+
|
458 |
+
if ( ! comments_open( $post->ID ) ) {
|
459 |
+
return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed on this post.' ), array( 'status' => 403 ) );
|
460 |
+
}
|
461 |
+
}
|
462 |
+
|
463 |
+
return true;
|
464 |
+
}
|
465 |
+
|
466 |
+
/**
|
467 |
+
* Check if a given request has access to update a comment
|
468 |
+
*
|
469 |
+
* @param WP_REST_Request $request Full details about the request.
|
470 |
+
* @return bool|WP_Error
|
471 |
+
*/
|
472 |
+
public function update_item_permissions_check( $request ) {
|
473 |
+
|
474 |
+
$id = (int) $request['id'];
|
475 |
+
|
476 |
+
$comment = get_comment( $id );
|
477 |
+
|
478 |
+
if ( $comment && ! $this->check_edit_permission( $comment ) ) {
|
479 |
+
return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you can not edit this comment.' ), array( 'status' => 403 ) );
|
480 |
+
}
|
481 |
+
|
482 |
+
return true;
|
483 |
+
}
|
484 |
+
|
485 |
+
/**
|
486 |
+
* Check if a given request has access to delete a comment
|
487 |
+
*
|
488 |
+
* @param WP_REST_Request $request Full details about the request.
|
489 |
+
* @return bool|WP_Error
|
490 |
+
*/
|
491 |
+
public function delete_item_permissions_check( $request ) {
|
492 |
+
return $this->update_item_permissions_check( $request );
|
493 |
+
}
|
494 |
+
|
495 |
+
/**
|
496 |
+
* Prepare a single comment output for response.
|
497 |
+
*
|
498 |
+
* @param object $comment Comment object.
|
499 |
+
* @param WP_REST_Request $request Request object.
|
500 |
+
* @return array $fields
|
501 |
+
*/
|
502 |
+
public function prepare_item_for_response( $comment, $request ) {
|
503 |
+
$data = array(
|
504 |
+
'id' => (int) $comment->comment_ID,
|
505 |
+
'post' => (int) $comment->comment_post_ID,
|
506 |
+
'parent' => (int) $comment->comment_parent,
|
507 |
+
'author' => (int) $comment->user_id,
|
508 |
+
'author_name' => $comment->comment_author,
|
509 |
+
'author_email' => $comment->comment_author_email,
|
510 |
+
'author_url' => $comment->comment_author_url,
|
511 |
+
'author_ip' => $comment->comment_author_IP,
|
512 |
+
'author_avatar_urls' => rest_get_avatar_urls( $comment->comment_author_email ),
|
513 |
+
'author_user_agent' => $comment->comment_agent,
|
514 |
+
'date' => rest_mysql_to_rfc3339( $comment->comment_date ),
|
515 |
+
'date_gmt' => rest_mysql_to_rfc3339( $comment->comment_date_gmt ),
|
516 |
+
'content' => array(
|
517 |
+
'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment ),
|
518 |
+
'raw' => $comment->comment_content,
|
519 |
+
),
|
520 |
+
'karma' => (int) $comment->comment_karma,
|
521 |
+
'link' => get_comment_link( $comment ),
|
522 |
+
'status' => $this->prepare_status_response( $comment->comment_approved ),
|
523 |
+
'type' => get_comment_type( $comment->comment_ID ),
|
524 |
+
);
|
525 |
+
|
526 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
527 |
+
$data = $this->filter_response_by_context( $data, $context );
|
528 |
+
$data = $this->add_additional_fields_to_object( $data, $request );
|
529 |
+
|
530 |
+
// Wrap the data in a response object
|
531 |
+
$data = rest_ensure_response( $data );
|
532 |
+
|
533 |
+
$data->add_links( $this->prepare_links( $comment ) );
|
534 |
+
|
535 |
+
return apply_filters( 'rest_prepare_comment', $data, $comment, $request );
|
536 |
+
}
|
537 |
+
|
538 |
+
/**
|
539 |
+
* Prepare links for the request.
|
540 |
+
*
|
541 |
+
* @param object $comment Comment object.
|
542 |
+
* @return array Links for the given comment.
|
543 |
+
*/
|
544 |
+
protected function prepare_links( $comment ) {
|
545 |
+
$links = array(
|
546 |
+
'self' => array(
|
547 |
+
'href' => rest_url( '/wp/v2/comments/' . $comment->comment_ID ),
|
548 |
+
),
|
549 |
+
'collection' => array(
|
550 |
+
'href' => rest_url( '/wp/v2/comments' ),
|
551 |
+
),
|
552 |
+
);
|
553 |
+
|
554 |
+
if ( 0 !== (int) $comment->user_id ) {
|
555 |
+
$links['author'] = array(
|
556 |
+
'href' => rest_url( '/wp/v2/users/' . $comment->user_id ),
|
557 |
+
'embeddable' => true,
|
558 |
+
);
|
559 |
+
}
|
560 |
+
|
561 |
+
if ( 0 !== (int) $comment->comment_post_ID ) {
|
562 |
+
$post = get_post( $comment->comment_post_ID );
|
563 |
+
if ( ! empty( $post->ID ) ) {
|
564 |
+
$posts_controller = new WP_REST_Posts_Controller( $post->post_type );
|
565 |
+
$base = $posts_controller->get_post_type_base( $post->post_type );
|
566 |
+
|
567 |
+
$links['up'] = array(
|
568 |
+
'href' => rest_url( '/wp/v2/' . $base . '/' . $comment->comment_post_ID ),
|
569 |
+
'embeddable' => true,
|
570 |
+
'post_type' => $post->post_type,
|
571 |
+
);
|
572 |
+
}
|
573 |
+
}
|
574 |
+
|
575 |
+
if ( 0 !== (int) $comment->comment_parent ) {
|
576 |
+
$links['in-reply-to'] = array(
|
577 |
+
'href' => rest_url( sprintf( '/wp/v2/comments/%d', (int) $comment->comment_parent ) ),
|
578 |
+
'embeddable' => true,
|
579 |
+
);
|
580 |
+
}
|
581 |
+
|
582 |
+
return $links;
|
583 |
+
}
|
584 |
+
|
585 |
+
/**
|
586 |
+
* Filter query parameters for comments collection endpoint.
|
587 |
+
*
|
588 |
+
* Prepares arguments before passing them along to WP_Comment_Query.
|
589 |
+
*
|
590 |
+
* @param WP_REST_Request $request Request object.
|
591 |
+
* @return array $prepared_args
|
592 |
+
*/
|
593 |
+
protected function prepare_items_query( $request ) {
|
594 |
+
$order_by = sanitize_key( $request['orderby'] );
|
595 |
+
|
596 |
+
$prepared_args = array(
|
597 |
+
'number' => $request['per_page'],
|
598 |
+
'post_id' => $request['post'] ? $request['post'] : '',
|
599 |
+
'parent' => isset( $request['parent'] ) ? $request['parent'] : '',
|
600 |
+
'search' => $request['search'],
|
601 |
+
'orderby' => $this->normalize_query_param( $order_by ),
|
602 |
+
'order' => $request['order'],
|
603 |
+
'status' => 'approve',
|
604 |
+
'type' => 'comment',
|
605 |
+
);
|
606 |
+
|
607 |
+
$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
|
608 |
+
|
609 |
+
if ( current_user_can( 'edit_posts' ) ) {
|
610 |
+
$protected_args = array(
|
611 |
+
'user' => $request['user'] ? $request['user'] : '',
|
612 |
+
'status' => $request['status'],
|
613 |
+
'type' => isset( $request['type'] ) ? $request['type'] : '',
|
614 |
+
'author_email' => isset( $request['author_email'] ) ? $request['author_email'] : '',
|
615 |
+
'karma' => isset( $request['karma'] ) ? $request['karma'] : '',
|
616 |
+
'post_author' => isset( $request['post_author'] ) ? $request['post_author'] : '',
|
617 |
+
'post_name' => isset( $request['post_slug'] ) ? $request['post_slug'] : '',
|
618 |
+
'post_parent' => isset( $request['post_parent'] ) ? $request['post_parent'] : '',
|
619 |
+
'post_status' => isset( $request['post_status'] ) ? $request['post_status'] : '',
|
620 |
+
'post_type' => isset( $request['post_type'] ) ? $request['post_type'] : '',
|
621 |
+
);
|
622 |
+
|
623 |
+
$prepared_args = array_merge( $prepared_args, $protected_args );
|
624 |
+
}
|
625 |
+
|
626 |
+
return $prepared_args;
|
627 |
+
}
|
628 |
+
|
629 |
+
/**
|
630 |
+
* Prepend internal property prefix to query parameters to match our response fields.
|
631 |
+
*
|
632 |
+
* @param string $query_param
|
633 |
+
* @return string $normalized
|
634 |
+
*/
|
635 |
+
protected function normalize_query_param( $query_param ) {
|
636 |
+
$prefix = 'comment_';
|
637 |
+
|
638 |
+
switch ( $query_param ) {
|
639 |
+
case 'id':
|
640 |
+
$normalized = $prefix . 'ID';
|
641 |
+
break;
|
642 |
+
case 'post':
|
643 |
+
$normalized = $prefix . 'post_ID';
|
644 |
+
break;
|
645 |
+
case 'parent':
|
646 |
+
$normalized = $prefix . 'parent';
|
647 |
+
break;
|
648 |
+
default:
|
649 |
+
$normalized = $prefix . $query_param;
|
650 |
+
break;
|
651 |
+
}
|
652 |
+
|
653 |
+
return $normalized;
|
654 |
+
}
|
655 |
+
|
656 |
+
/**
|
657 |
+
* Check comment_approved to set comment status for single comment output.
|
658 |
+
*
|
659 |
+
* @param string|int $comment_approved
|
660 |
+
* @return string $status
|
661 |
+
*/
|
662 |
+
protected function prepare_status_response( $comment_approved ) {
|
663 |
+
|
664 |
+
switch ( $comment_approved ) {
|
665 |
+
case 'hold':
|
666 |
+
case '0':
|
667 |
+
$status = 'hold';
|
668 |
+
break;
|
669 |
+
|
670 |
+
case 'approve':
|
671 |
+
case '1':
|
672 |
+
$status = 'approved';
|
673 |
+
break;
|
674 |
+
|
675 |
+
case 'spam':
|
676 |
+
case 'trash':
|
677 |
+
default:
|
678 |
+
$status = $comment_approved;
|
679 |
+
break;
|
680 |
+
}
|
681 |
+
|
682 |
+
return $status;
|
683 |
+
}
|
684 |
+
|
685 |
+
/**
|
686 |
+
* Prepare a single comment to be inserted into the database.
|
687 |
+
*
|
688 |
+
* @param WP_REST_Request $request Request object.
|
689 |
+
* @return array|WP_Error $prepared_comment
|
690 |
+
*/
|
691 |
+
protected function prepare_item_for_database( $request ) {
|
692 |
+
$prepared_comment = array();
|
693 |
+
|
694 |
+
if ( isset( $request['content'] ) ) {
|
695 |
+
$prepared_comment['comment_content'] = $request['content'];
|
696 |
+
}
|
697 |
+
|
698 |
+
if ( isset( $request['post'] ) ) {
|
699 |
+
$prepared_comment['comment_post_ID'] = (int) $request['post'];
|
700 |
+
}
|
701 |
+
|
702 |
+
if ( isset( $request['parent'] ) ) {
|
703 |
+
$prepared_comment['comment_parent'] = $request['parent'];
|
704 |
+
}
|
705 |
+
|
706 |
+
if ( isset( $request['author'] ) ) {
|
707 |
+
$prepared_comment['user_id'] = $request['author'];
|
708 |
+
}
|
709 |
+
|
710 |
+
if ( isset( $request['author_name'] ) ) {
|
711 |
+
$prepared_comment['comment_author'] = $request['author_name'];
|
712 |
+
}
|
713 |
+
|
714 |
+
if ( isset( $request['author_email'] ) ) {
|
715 |
+
$prepared_comment['comment_author_email'] = $request['author_email'];
|
716 |
+
}
|
717 |
+
|
718 |
+
if ( isset( $request['author_url'] ) ) {
|
719 |
+
$prepared_comment['comment_author_url'] = $request['author_url'];
|
720 |
+
}
|
721 |
+
|
722 |
+
if ( isset( $request['type'] ) ) {
|
723 |
+
$prepared_comment['comment_type'] = $request['type'];
|
724 |
+
}
|
725 |
+
|
726 |
+
if ( isset( $request['karma'] ) ) {
|
727 |
+
$prepared_comment['comment_karma'] = $request['karma'] ;
|
728 |
+
}
|
729 |
+
|
730 |
+
if ( ! empty( $request['date'] ) ) {
|
731 |
+
$date_data = rest_get_date_with_gmt( $request['date'] );
|
732 |
+
|
733 |
+
if ( ! empty( $date_data ) ) {
|
734 |
+
list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) =
|
735 |
+
$date_data;
|
736 |
+
} else {
|
737 |
+
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ), array( 'status' => 400 ) );
|
738 |
+
}
|
739 |
+
} elseif ( ! empty( $request['date_gmt'] ) ) {
|
740 |
+
$date_data = rest_get_date_with_gmt( $request['date_gmt'], true );
|
741 |
+
|
742 |
+
if ( ! empty( $date_data ) ) {
|
743 |
+
list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
|
744 |
+
} else {
|
745 |
+
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ), array( 'status' => 400 ) );
|
746 |
+
}
|
747 |
+
}
|
748 |
+
|
749 |
+
return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request );
|
750 |
+
}
|
751 |
+
|
752 |
+
/**
|
753 |
+
* Get the Comment's schema, conforming to JSON Schema
|
754 |
+
*
|
755 |
+
* @return array
|
756 |
+
*/
|
757 |
+
public function get_item_schema() {
|
758 |
+
$avatar_properties = array();
|
759 |
+
|
760 |
+
$avatar_sizes = rest_get_avatar_sizes();
|
761 |
+
foreach ( $avatar_sizes as $size ) {
|
762 |
+
$avatar_properties[ $size ] = array(
|
763 |
+
'description' => 'Avatar URL with image size of ' . $size . ' pixels.',
|
764 |
+
'type' => 'uri',
|
765 |
+
'context' => array( 'embed', 'view', 'edit' ),
|
766 |
+
);
|
767 |
+
}
|
768 |
+
|
769 |
+
$schema = array(
|
770 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
771 |
+
'title' => 'comment',
|
772 |
+
'type' => 'object',
|
773 |
+
'properties' => array(
|
774 |
+
'id' => array(
|
775 |
+
'description' => 'Unique identifier for the object.',
|
776 |
+
'type' => 'integer',
|
777 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
778 |
+
'readonly' => true,
|
779 |
+
),
|
780 |
+
'author' => array(
|
781 |
+
'description' => 'The ID of the user object, if author was a user.',
|
782 |
+
'type' => 'integer',
|
783 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
784 |
+
),
|
785 |
+
'author_avatar_urls' => array(
|
786 |
+
'description' => 'Avatar URLs for the object author.',
|
787 |
+
'type' => 'object',
|
788 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
789 |
+
'readonly' => true,
|
790 |
+
'properties' => $avatar_properties,
|
791 |
+
),
|
792 |
+
'author_email' => array(
|
793 |
+
'description' => 'Email address for the object author.',
|
794 |
+
'type' => 'string',
|
795 |
+
'format' => 'email',
|
796 |
+
'context' => array( 'edit' ),
|
797 |
+
),
|
798 |
+
'author_ip' => array(
|
799 |
+
'description' => 'IP address for the object author.',
|
800 |
+
'type' => 'string',
|
801 |
+
'context' => array( 'edit' ),
|
802 |
+
'readonly' => true,
|
803 |
+
),
|
804 |
+
'author_name' => array(
|
805 |
+
'description' => 'Display name for the object author.',
|
806 |
+
'type' => 'string',
|
807 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
808 |
+
),
|
809 |
+
'author_url' => array(
|
810 |
+
'description' => 'URL for the object author.',
|
811 |
+
'type' => 'string',
|
812 |
+
'format' => 'uri',
|
813 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
814 |
+
),
|
815 |
+
'author_user_agent' => array(
|
816 |
+
'description' => 'User agent for the object author.',
|
817 |
+
'type' => 'string',
|
818 |
+
'context' => array( 'edit' ),
|
819 |
+
'readonly' => true,
|
820 |
+
),
|
821 |
+
'content' => array(
|
822 |
+
'description' => 'The content for the object.',
|
823 |
+
'type' => 'object',
|
824 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
825 |
+
'properties' => array(
|
826 |
+
'raw' => array(
|
827 |
+
'description' => 'Content for the object, as it exists in the database.',
|
828 |
+
'type' => 'string',
|
829 |
+
'context' => array( 'edit' ),
|
830 |
+
),
|
831 |
+
'rendered' => array(
|
832 |
+
'description' => 'Content for the object, transformed for display.',
|
833 |
+
'type' => 'string',
|
834 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
835 |
+
),
|
836 |
+
),
|
837 |
+
),
|
838 |
+
'date' => array(
|
839 |
+
'description' => 'The date the object was published.',
|
840 |
+
'type' => 'string',
|
841 |
+
'format' => 'date-time',
|
842 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
843 |
+
),
|
844 |
+
'date_gmt' => array(
|
845 |
+
'description' => 'The date the object was published as GMT.',
|
846 |
+
'type' => 'string',
|
847 |
+
'format' => 'date-time',
|
848 |
+
'context' => array( 'edit' ),
|
849 |
+
),
|
850 |
+
'karma' => array(
|
851 |
+
'description' => 'Karma for the object.',
|
852 |
+
'type' => 'integer',
|
853 |
+
'context' => array( 'edit' ),
|
854 |
+
'readonly' => true,
|
855 |
+
),
|
856 |
+
'link' => array(
|
857 |
+
'description' => 'URL to the object.',
|
858 |
+
'type' => 'string',
|
859 |
+
'format' => 'uri',
|
860 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
861 |
+
'readonly' => true,
|
862 |
+
),
|
863 |
+
'parent' => array(
|
864 |
+
'description' => 'The ID for the parent of the object.',
|
865 |
+
'type' => 'integer',
|
866 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
867 |
+
),
|
868 |
+
'post' => array(
|
869 |
+
'description' => 'The ID of the associated post object.',
|
870 |
+
'type' => 'integer',
|
871 |
+
'context' => array( 'view', 'edit' ),
|
872 |
+
),
|
873 |
+
'status' => array(
|
874 |
+
'description' => 'State of the object.',
|
875 |
+
'type' => 'string',
|
876 |
+
'context' => array( 'view', 'edit' ),
|
877 |
+
),
|
878 |
+
'type' => array(
|
879 |
+
'description' => 'Type of Comment for the object.',
|
880 |
+
'type' => 'string',
|
881 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
882 |
+
),
|
883 |
+
),
|
884 |
+
);
|
885 |
+
return $this->add_additional_fields_schema( $schema );
|
886 |
+
}
|
887 |
+
|
888 |
+
/**
|
889 |
+
* Get the query params for collections
|
890 |
+
*
|
891 |
+
* @return array
|
892 |
+
*/
|
893 |
+
public function get_collection_params() {
|
894 |
+
$query_params = parent::get_collection_params();
|
895 |
+
$query_params['author_email'] = array(
|
896 |
+
'default' => null,
|
897 |
+
'description' => 'Limit result set to that from a specific author email.',
|
898 |
+
'format' => 'email',
|
899 |
+
'sanitize_callback' => 'sanitize_email',
|
900 |
+
'type' => 'string',
|
901 |
+
);
|
902 |
+
$query_params['karma'] = array(
|
903 |
+
'default' => null,
|
904 |
+
'description' => 'Limit result set to that of a particular comment karma.',
|
905 |
+
'sanitize_callback' => 'absint',
|
906 |
+
'type' => 'integer',
|
907 |
+
);
|
908 |
+
$query_params['parent'] = array(
|
909 |
+
'default' => null,
|
910 |
+
'description' => 'Limit result set to that of a specific comment parent id.',
|
911 |
+
'sanitize_callback' => 'absint',
|
912 |
+
'type' => 'integer',
|
913 |
+
);
|
914 |
+
$query_params['post'] = array(
|
915 |
+
'default' => null,
|
916 |
+
'description' => 'Limit result set to comments assigned to a specific post id.',
|
917 |
+
'sanitize_callback' => 'absint',
|
918 |
+
'type' => 'integer',
|
919 |
+
);
|
920 |
+
$query_params['post_author'] = array(
|
921 |
+
'default' => null,
|
922 |
+
'description' => 'Limit result set to comments associated with posts of a specific post author id.',
|
923 |
+
'sanitize_callback' => 'absint',
|
924 |
+
'type' => 'integer',
|
925 |
+
);
|
926 |
+
$query_params['post_slug'] = array(
|
927 |
+
'default' => null,
|
928 |
+
'description' => 'Limit result set to comments associated with posts of a specific post slug.',
|
929 |
+
'sanitize_callback' => 'sanitize_title',
|
930 |
+
'type' => 'string',
|
931 |
+
);
|
932 |
+
$query_params['post_parent'] = array(
|
933 |
+
'default' => null,
|
934 |
+
'description' => 'Limit result set to comments associated with posts of a specific post parent id.',
|
935 |
+
'sanitize_callback' => 'absint',
|
936 |
+
'type' => 'integer',
|
937 |
+
);
|
938 |
+
$query_params['post_status'] = array(
|
939 |
+
'default' => null,
|
940 |
+
'description' => 'Limit result set to comments associated with posts of a specific post status.',
|
941 |
+
'sanitize_callback' => 'sanitize_key',
|
942 |
+
'type' => 'string',
|
943 |
+
);
|
944 |
+
$query_params['post_type'] = array(
|
945 |
+
'default' => null,
|
946 |
+
'description' => 'Limit result set to comments associated with posts of a specific post type.',
|
947 |
+
'sanitize_callback' => 'sanitize_key',
|
948 |
+
'type' => 'string',
|
949 |
+
);
|
950 |
+
$query_params['status'] = array(
|
951 |
+
'default' => 'approve',
|
952 |
+
'description' => 'Limit result set to comments assigned a specific status.',
|
953 |
+
'sanitize_callback' => 'sanitize_key',
|
954 |
+
'type' => 'string',
|
955 |
+
);
|
956 |
+
$query_params['type'] = array(
|
957 |
+
'default' => 'comment',
|
958 |
+
'description' => 'Limit result set to comments assigned a specific type.',
|
959 |
+
'sanitize_callback' => 'sanitize_key',
|
960 |
+
'type' => 'string',
|
961 |
+
);
|
962 |
+
$query_params['user'] = array(
|
963 |
+
'default' => null,
|
964 |
+
'description' => 'Limit result set to comments assigned to a specific user id.',
|
965 |
+
'sanitize_callback' => 'absint',
|
966 |
+
'type' => 'integer',
|
967 |
+
);
|
968 |
+
return $query_params;
|
969 |
+
}
|
970 |
+
|
971 |
+
/**
|
972 |
+
* Set the comment_status of a given comment object when creating or updating a comment.
|
973 |
+
*
|
974 |
+
* @param string|int $new_status
|
975 |
+
* @param object $comment
|
976 |
+
* @return boolean $changed
|
977 |
+
*/
|
978 |
+
protected function handle_status_param( $new_status, $comment ) {
|
979 |
+
$old_status = wp_get_comment_status( $comment->comment_ID );
|
980 |
+
|
981 |
+
if ( $new_status === $old_status ) {
|
982 |
+
return false;
|
983 |
+
}
|
984 |
+
|
985 |
+
switch ( $new_status ) {
|
986 |
+
case 'approved' :
|
987 |
+
case 'approve':
|
988 |
+
case '1':
|
989 |
+
$changed = wp_set_comment_status( $comment->comment_ID, 'approve' );
|
990 |
+
break;
|
991 |
+
case 'hold':
|
992 |
+
case '0':
|
993 |
+
$changed = wp_set_comment_status( $comment->comment_ID, 'hold' );
|
994 |
+
break;
|
995 |
+
case 'spam' :
|
996 |
+
$changed = wp_spam_comment( $comment->comment_ID );
|
997 |
+
break;
|
998 |
+
case 'unspam' :
|
999 |
+
$changed = wp_unspam_comment( $comment->comment_ID );
|
1000 |
+
break;
|
1001 |
+
case 'trash' :
|
1002 |
+
$changed = wp_trash_comment( $comment->comment_ID );
|
1003 |
+
break;
|
1004 |
+
case 'untrash' :
|
1005 |
+
$changed = wp_untrash_comment( $comment->comment_ID );
|
1006 |
+
break;
|
1007 |
+
default :
|
1008 |
+
$changed = false;
|
1009 |
+
break;
|
1010 |
+
}
|
1011 |
+
|
1012 |
+
return $changed;
|
1013 |
+
}
|
1014 |
+
|
1015 |
+
/**
|
1016 |
+
* Check if we can read a post.
|
1017 |
+
*
|
1018 |
+
* Correctly handles posts with the inherit status.
|
1019 |
+
*
|
1020 |
+
* @param WP_Post $post Post Object.
|
1021 |
+
* @return boolean Can we read it?
|
1022 |
+
*/
|
1023 |
+
protected function check_read_post_permission( $post ) {
|
1024 |
+
$posts_controller = new WP_REST_Posts_Controller( $post->post_type );
|
1025 |
+
|
1026 |
+
return $posts_controller->check_read_permission( $post );
|
1027 |
+
}
|
1028 |
+
|
1029 |
+
/**
|
1030 |
+
* Check if we can read a comment.
|
1031 |
+
*
|
1032 |
+
* @param object $comment Comment object.
|
1033 |
+
* @return boolean Can we read it?
|
1034 |
+
*/
|
1035 |
+
protected function check_read_permission( $comment ) {
|
1036 |
+
|
1037 |
+
if ( 1 === (int) $comment->comment_approved ) {
|
1038 |
+
return true;
|
1039 |
+
}
|
1040 |
+
|
1041 |
+
if ( 0 === get_current_user_id() ) {
|
1042 |
+
return false;
|
1043 |
+
}
|
1044 |
+
|
1045 |
+
if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) {
|
1046 |
+
return true;
|
1047 |
+
}
|
1048 |
+
|
1049 |
+
return current_user_can( 'edit_comment', $comment->comment_ID );
|
1050 |
+
}
|
1051 |
+
|
1052 |
+
/**
|
1053 |
+
* Check if we can edit or delete a comment.
|
1054 |
+
*
|
1055 |
+
* @param object $comment Comment object.
|
1056 |
+
* @return boolean Can we edit or delete it?
|
1057 |
+
*/
|
1058 |
+
protected function check_edit_permission( $comment ) {
|
1059 |
+
if ( 0 === (int) get_current_user_id() ) {
|
1060 |
+
return false;
|
1061 |
+
}
|
1062 |
+
|
1063 |
+
if ( ! current_user_can( 'moderate_comments' ) ) {
|
1064 |
+
return false;
|
1065 |
+
}
|
1066 |
+
|
1067 |
+
return current_user_can( 'edit_comment', $comment->comment_ID );
|
1068 |
+
}
|
1069 |
+
}
|
lib/endpoints/class-wp-rest-controller.php
ADDED
@@ -0,0 +1,505 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
|
4 |
+
abstract class WP_REST_Controller {
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Register the routes for the objects of the controller.
|
8 |
+
*/
|
9 |
+
public function register_routes() {
|
10 |
+
_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overriden' ), 'WPAPI-2.0' );
|
11 |
+
}
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Get a collection of items
|
15 |
+
*
|
16 |
+
* @param WP_REST_Request $request Full data about the request.
|
17 |
+
* @return WP_Error|WP_REST_Response
|
18 |
+
*/
|
19 |
+
public function get_items( $request ) {
|
20 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Get one item from the collection
|
25 |
+
*
|
26 |
+
* @param WP_REST_Request $request Full data about the request.
|
27 |
+
* @return WP_Error|WP_REST_Response
|
28 |
+
*/
|
29 |
+
public function get_item( $request ) {
|
30 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Create one item from the collection
|
35 |
+
*
|
36 |
+
* @param WP_REST_Request $request Full data about the request.
|
37 |
+
* @return WP_Error|WP_REST_Request
|
38 |
+
*/
|
39 |
+
public function create_item( $request ) {
|
40 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Update one item from the collection
|
45 |
+
*
|
46 |
+
* @param WP_REST_Request $request Full data about the request.
|
47 |
+
* @return WP_Error|WP_REST_Request
|
48 |
+
*/
|
49 |
+
public function update_item( $request ) {
|
50 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Delete one item from the collection
|
55 |
+
*
|
56 |
+
* @param WP_REST_Request $request Full data about the request.
|
57 |
+
* @return WP_Error|WP_REST_Request
|
58 |
+
*/
|
59 |
+
public function delete_item( $request ) {
|
60 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Check if a given request has access to get items
|
65 |
+
*
|
66 |
+
* @param WP_REST_Request $request Full data about the request.
|
67 |
+
* @return WP_Error|bool
|
68 |
+
*/
|
69 |
+
public function get_items_permissions_check( $request ) {
|
70 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Check if a given request has access to get a specific item
|
75 |
+
*
|
76 |
+
* @param WP_REST_Request $request Full data about the request.
|
77 |
+
* @return WP_Error|bool
|
78 |
+
*/
|
79 |
+
public function get_item_permissions_check( $request ) {
|
80 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Check if a given request has access to create items
|
85 |
+
*
|
86 |
+
* @param WP_REST_Request $request Full data about the request.
|
87 |
+
* @return WP_Error|bool
|
88 |
+
*/
|
89 |
+
public function create_item_permissions_check( $request ) {
|
90 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Check if a given request has access to update a specific item
|
95 |
+
*
|
96 |
+
* @param WP_REST_Request $request Full data about the request.
|
97 |
+
* @return WP_Error|bool
|
98 |
+
*/
|
99 |
+
public function update_item_permissions_check( $request ) {
|
100 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Check if a given request has access to delete a specific item
|
105 |
+
*
|
106 |
+
* @param WP_REST_Request $request Full data about the request.
|
107 |
+
* @return WP_Error|bool
|
108 |
+
*/
|
109 |
+
public function delete_item_permissions_check( $request ) {
|
110 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
111 |
+
}
|
112 |
+
|
113 |
+
/**
|
114 |
+
* Prepare the item for create or update operation
|
115 |
+
*
|
116 |
+
* @param WP_REST_Request $request Request object
|
117 |
+
* @return WP_Error|object $prepared_item
|
118 |
+
*/
|
119 |
+
protected function prepare_item_for_database( $request ) {
|
120 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Prepare the item for the REST response
|
125 |
+
*
|
126 |
+
* @param mixed $item WordPress representation of the item.
|
127 |
+
* @param WP_REST_Request $request Request object.
|
128 |
+
* @return mixed
|
129 |
+
*/
|
130 |
+
public function prepare_item_for_response( $item, $request ) {
|
131 |
+
return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* Prepare a response for inserting into a collection.
|
136 |
+
*
|
137 |
+
* @param WP_REST_Response $response Response object.
|
138 |
+
* @return array Response data, ready for insertion into collection data.
|
139 |
+
*/
|
140 |
+
public function prepare_response_for_collection( $response ) {
|
141 |
+
if ( ! ( $response instanceof WP_REST_Response ) ) {
|
142 |
+
return $response;
|
143 |
+
}
|
144 |
+
|
145 |
+
$data = (array) $response->get_data();
|
146 |
+
$links = WP_REST_Server::get_response_links( $response );
|
147 |
+
if ( ! empty( $links ) ) {
|
148 |
+
$data['_links'] = $links;
|
149 |
+
}
|
150 |
+
|
151 |
+
return $data;
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Filter a response based on the context defined in the schema
|
156 |
+
*
|
157 |
+
* @param array $data
|
158 |
+
* @param string $context
|
159 |
+
* @return array
|
160 |
+
*/
|
161 |
+
public function filter_response_by_context( $data, $context ) {
|
162 |
+
|
163 |
+
$schema = $this->get_item_schema();
|
164 |
+
foreach ( $data as $key => $value ) {
|
165 |
+
if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) {
|
166 |
+
continue;
|
167 |
+
}
|
168 |
+
|
169 |
+
if ( ! in_array( $context, $schema['properties'][ $key ]['context'] ) ) {
|
170 |
+
unset( $data[ $key ] );
|
171 |
+
}
|
172 |
+
|
173 |
+
if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
|
174 |
+
foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
|
175 |
+
if ( empty( $details['context'] ) ) {
|
176 |
+
continue;
|
177 |
+
}
|
178 |
+
if ( ! in_array( $context, $details['context'] ) ) {
|
179 |
+
unset( $data[ $key ][ $attribute ] );
|
180 |
+
}
|
181 |
+
}
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
return $data;
|
186 |
+
}
|
187 |
+
|
188 |
+
/**
|
189 |
+
* Get the item's schema, conforming to JSON Schema
|
190 |
+
*
|
191 |
+
* @return array
|
192 |
+
*/
|
193 |
+
public function get_item_schema() {
|
194 |
+
return $this->add_additional_fields_schema( array() );
|
195 |
+
}
|
196 |
+
|
197 |
+
/**
|
198 |
+
* Get the item's schema for display / public consumption purposes.
|
199 |
+
*
|
200 |
+
* @return array
|
201 |
+
*/
|
202 |
+
public function get_public_item_schema() {
|
203 |
+
|
204 |
+
$schema = $this->get_item_schema();
|
205 |
+
|
206 |
+
foreach ( $schema['properties'] as &$property ) {
|
207 |
+
if ( isset( $property['arg_options'] ) ) {
|
208 |
+
unset( $property['arg_options'] );
|
209 |
+
}
|
210 |
+
}
|
211 |
+
|
212 |
+
return $schema;
|
213 |
+
}
|
214 |
+
|
215 |
+
/**
|
216 |
+
* Get the query params for collections
|
217 |
+
*
|
218 |
+
* @return array
|
219 |
+
*/
|
220 |
+
public function get_collection_params() {
|
221 |
+
return array(
|
222 |
+
'page' => array(
|
223 |
+
'description' => 'Current page of the collection.',
|
224 |
+
'type' => 'integer',
|
225 |
+
'default' => 1,
|
226 |
+
'sanitize_callback' => 'absint',
|
227 |
+
),
|
228 |
+
'per_page' => array(
|
229 |
+
'description' => 'Maximum number of items to be returned in result set.',
|
230 |
+
'type' => 'integer',
|
231 |
+
'default' => 10,
|
232 |
+
'sanitize_callback' => 'absint',
|
233 |
+
),
|
234 |
+
'search' => array(
|
235 |
+
'description' => 'Limit results to those matching a string.',
|
236 |
+
'type' => 'string',
|
237 |
+
'sanitize_callback' => 'sanitize_text_field',
|
238 |
+
),
|
239 |
+
);
|
240 |
+
}
|
241 |
+
|
242 |
+
/**
|
243 |
+
* Add the values from additional fields to a data object
|
244 |
+
*
|
245 |
+
* @param array $object
|
246 |
+
* @param WP_REST_Request $request
|
247 |
+
* @return array modified object with additional fields
|
248 |
+
*/
|
249 |
+
protected function add_additional_fields_to_object( $object, $request ) {
|
250 |
+
|
251 |
+
$additional_fields = $this->get_additional_fields();
|
252 |
+
|
253 |
+
foreach ( $additional_fields as $field_name => $field_options ) {
|
254 |
+
|
255 |
+
if ( ! $field_options['get_callback'] ) {
|
256 |
+
continue;
|
257 |
+
}
|
258 |
+
|
259 |
+
$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request );
|
260 |
+
}
|
261 |
+
|
262 |
+
return $object;
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Update the values of additional fields added to a data object.
|
267 |
+
*
|
268 |
+
* @param array $object
|
269 |
+
* @param WP_REST_Request $request
|
270 |
+
*/
|
271 |
+
protected function update_additional_fields_for_object( $object, $request ) {
|
272 |
+
|
273 |
+
$additional_fields = $this->get_additional_fields();
|
274 |
+
|
275 |
+
foreach ( $additional_fields as $field_name => $field_options ) {
|
276 |
+
|
277 |
+
if ( ! $field_options['update_callback'] ) {
|
278 |
+
continue;
|
279 |
+
}
|
280 |
+
|
281 |
+
// Don't run the update callbacks if the data wasn't passed in the request
|
282 |
+
if ( ! isset( $request[ $field_name ] ) ) {
|
283 |
+
continue;
|
284 |
+
}
|
285 |
+
|
286 |
+
$result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request );
|
287 |
+
}
|
288 |
+
}
|
289 |
+
|
290 |
+
/**
|
291 |
+
* Add the schema from additional fields to an schema array
|
292 |
+
*
|
293 |
+
* The type of object is inferred from the passed schema.
|
294 |
+
*
|
295 |
+
* @param array $schema Schema array
|
296 |
+
*/
|
297 |
+
protected function add_additional_fields_schema( $schema ) {
|
298 |
+
if ( ! $schema || ! isset( $schema['title'] ) ) {
|
299 |
+
return $schema;
|
300 |
+
}
|
301 |
+
|
302 |
+
/**
|
303 |
+
* Can't use $this->get_object_type otherwise we cause an inf loop
|
304 |
+
*/
|
305 |
+
$object_type = $schema['title'];
|
306 |
+
|
307 |
+
$additional_fields = $this->get_additional_fields( $object_type );
|
308 |
+
|
309 |
+
foreach ( $additional_fields as $field_name => $field_options ) {
|
310 |
+
if ( ! $field_options['schema'] ) {
|
311 |
+
continue;
|
312 |
+
}
|
313 |
+
|
314 |
+
$schema['properties'][ $field_name ] = $field_options['schema'];
|
315 |
+
}
|
316 |
+
|
317 |
+
return $schema;
|
318 |
+
}
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Get all the registered additional fields for a given object-type
|
322 |
+
*
|
323 |
+
* @param string $object_type
|
324 |
+
* @return array
|
325 |
+
*/
|
326 |
+
protected function get_additional_fields( $object_type = null ) {
|
327 |
+
|
328 |
+
if ( ! $object_type ) {
|
329 |
+
$object_type = $this->get_object_type();
|
330 |
+
}
|
331 |
+
|
332 |
+
if ( ! $object_type ) {
|
333 |
+
return array();
|
334 |
+
}
|
335 |
+
|
336 |
+
global $wp_rest_additional_fields;
|
337 |
+
|
338 |
+
if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
|
339 |
+
return array();
|
340 |
+
}
|
341 |
+
|
342 |
+
return $wp_rest_additional_fields[ $object_type ];
|
343 |
+
}
|
344 |
+
|
345 |
+
/**
|
346 |
+
* Get the object type this controller is responsible for managing.
|
347 |
+
*
|
348 |
+
* @return string
|
349 |
+
*/
|
350 |
+
protected function get_object_type() {
|
351 |
+
$schema = $this->get_item_schema();
|
352 |
+
|
353 |
+
if ( ! $schema || ! isset( $schema['title'] ) ) {
|
354 |
+
return null;
|
355 |
+
}
|
356 |
+
|
357 |
+
return $schema['title'];
|
358 |
+
}
|
359 |
+
|
360 |
+
/**
|
361 |
+
* Get an array of endpoint arguments from the item schema for the controller.
|
362 |
+
*
|
363 |
+
* @param $add_required_flag Whether to use the 'required' flag from the schema proprties.
|
364 |
+
* This is because update requests will not have any required params
|
365 |
+
* Where as create requests will.
|
366 |
+
* @return array
|
367 |
+
*/
|
368 |
+
public function get_endpoint_args_for_item_schema( $add_required_flag = true ) {
|
369 |
+
|
370 |
+
$schema = $this->get_item_schema();
|
371 |
+
$schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
|
372 |
+
$endpoint_args = array();
|
373 |
+
|
374 |
+
foreach ( $schema_properties as $field_id => $params ) {
|
375 |
+
|
376 |
+
// Anything marked as readonly should not be a arg
|
377 |
+
if ( ! empty( $params['readonly'] ) ) {
|
378 |
+
continue;
|
379 |
+
}
|
380 |
+
|
381 |
+
$endpoint_args[ $field_id ] = array(
|
382 |
+
'validate_callback' => array( $this, 'validate_schema_property' ),
|
383 |
+
'sanitize_callback' => array( $this, 'sanitize_schema_property' ),
|
384 |
+
);
|
385 |
+
|
386 |
+
if ( isset( $params['default'] ) ) {
|
387 |
+
$endpoint_args[ $field_id ]['default'] = $params['default'];
|
388 |
+
}
|
389 |
+
|
390 |
+
if ( $add_required_flag && ! empty( $params['required'] ) ) {
|
391 |
+
$endpoint_args[ $field_id ]['required'] = true;
|
392 |
+
}
|
393 |
+
|
394 |
+
// Merge in any options provided by the schema property
|
395 |
+
if ( isset( $params['arg_options'] ) ) {
|
396 |
+
$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
|
397 |
+
}
|
398 |
+
}
|
399 |
+
|
400 |
+
return $endpoint_args;
|
401 |
+
}
|
402 |
+
|
403 |
+
/**
|
404 |
+
* Validate an parameter value that's based on a property from the item schema.
|
405 |
+
*
|
406 |
+
* @param mixed $value
|
407 |
+
* @param WP_REST_Request $request
|
408 |
+
* @param string $parameter
|
409 |
+
* @return WP_Error|bool
|
410 |
+
*/
|
411 |
+
public function validate_schema_property( $value, $request, $parameter ) {
|
412 |
+
|
413 |
+
/**
|
414 |
+
* We don't currently validate against empty values, as lots of checks
|
415 |
+
* can unintentially fail, as the callback will often handle an empty
|
416 |
+
* value it's self.
|
417 |
+
*/
|
418 |
+
if ( ! $value ) {
|
419 |
+
return true;
|
420 |
+
}
|
421 |
+
|
422 |
+
$schema = $this->get_item_schema();
|
423 |
+
|
424 |
+
if ( ! isset( $schema['properties'][ $parameter ] ) ) {
|
425 |
+
return true;
|
426 |
+
}
|
427 |
+
|
428 |
+
$property = $schema['properties'][ $parameter ];
|
429 |
+
|
430 |
+
if ( ! empty( $property['enum'] ) ) {
|
431 |
+
if ( ! in_array( $value, $property['enum'] ) ) {
|
432 |
+
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not one of %s' ), $parameter, implode( ', ', $property['enum'] ) ) );
|
433 |
+
}
|
434 |
+
}
|
435 |
+
|
436 |
+
if ( 'integer' === $property['type'] && ! is_numeric( $value ) ) {
|
437 |
+
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'integer' ) );
|
438 |
+
}
|
439 |
+
|
440 |
+
if ( 'string' === $property['type']&& ! is_string( $value ) ) {
|
441 |
+
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'string' ) );
|
442 |
+
}
|
443 |
+
|
444 |
+
if ( isset( $property['format'] ) ) {
|
445 |
+
switch ( $property['format'] ) {
|
446 |
+
case 'date-time' :
|
447 |
+
if ( ! rest_parse_date( $value ) ) {
|
448 |
+
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
|
449 |
+
}
|
450 |
+
break;
|
451 |
+
|
452 |
+
case 'email' :
|
453 |
+
if ( ! is_email( $value ) ) {
|
454 |
+
return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
|
455 |
+
}
|
456 |
+
break;
|
457 |
+
}
|
458 |
+
}
|
459 |
+
|
460 |
+
return true;
|
461 |
+
}
|
462 |
+
|
463 |
+
/**
|
464 |
+
* Sanitize an parameter value that's based on a property from the item schema.
|
465 |
+
*
|
466 |
+
* @param mixed $value
|
467 |
+
* @param WP_REST_Request $request
|
468 |
+
* @param string $parameter
|
469 |
+
* @return WP_Error|bool
|
470 |
+
*/
|
471 |
+
public function sanitize_schema_property( $value, $request, $parameter ) {
|
472 |
+
|
473 |
+
$schema = $this->get_item_schema();
|
474 |
+
|
475 |
+
if ( ! isset( $schema['properties'][ $parameter ] ) ) {
|
476 |
+
return true;
|
477 |
+
}
|
478 |
+
|
479 |
+
$property = $schema['properties'][ $parameter ];
|
480 |
+
|
481 |
+
if ( 'integer' === $property['type'] ) {
|
482 |
+
return intval( $value );
|
483 |
+
}
|
484 |
+
|
485 |
+
if ( isset( $property['format'] ) ) {
|
486 |
+
switch ( $property['format'] ) {
|
487 |
+
case 'date-time' :
|
488 |
+
return sanitize_text_field( $value );
|
489 |
+
|
490 |
+
case 'email' :
|
491 |
+
// as sanitize_email is very lossy, we just want to
|
492 |
+
// make sure the string is safe
|
493 |
+
if ( sanitize_email( $value ) ) {
|
494 |
+
return sanitize_email( $value );
|
495 |
+
}
|
496 |
+
return sanitize_text_field( $value );
|
497 |
+
|
498 |
+
case 'uri' :
|
499 |
+
return esc_url_raw( $value );
|
500 |
+
}
|
501 |
+
}
|
502 |
+
|
503 |
+
return $value;
|
504 |
+
}
|
505 |
+
}
|
lib/endpoints/class-wp-rest-meta-controller.php
ADDED
@@ -0,0 +1,432 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Metadata base class.
|
4 |
+
*/
|
5 |
+
abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
|
6 |
+
/**
|
7 |
+
* Associated object type.
|
8 |
+
*
|
9 |
+
* @var string Type slug ("post", "user", or "comment")
|
10 |
+
*/
|
11 |
+
protected $parent_type = null;
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Base path for parent meta type endpoints.
|
15 |
+
*
|
16 |
+
* @var string
|
17 |
+
*/
|
18 |
+
protected $parent_base = null;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Construct the API handler object.
|
22 |
+
*/
|
23 |
+
public function __construct() {
|
24 |
+
if ( empty( $this->parent_type ) ) {
|
25 |
+
_doing_it_wrong( 'WP_REST_Meta_Controller::__construct', __( 'The object type must be overridden' ), 'WPAPI-2.0' );
|
26 |
+
return;
|
27 |
+
}
|
28 |
+
if ( empty( $this->parent_base ) ) {
|
29 |
+
_doing_it_wrong( 'WP_REST_Meta_Controller::__construct', __( 'The parent base must be overridden' ), 'WPAPI-2.0' );
|
30 |
+
return;
|
31 |
+
}
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Register the meta-related routes.
|
36 |
+
*/
|
37 |
+
public function register_routes() {
|
38 |
+
register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/meta', array(
|
39 |
+
array(
|
40 |
+
'methods' => WP_REST_Server::READABLE,
|
41 |
+
'callback' => array( $this, 'get_items' ),
|
42 |
+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
43 |
+
'args' => array(
|
44 |
+
'context' => array(
|
45 |
+
'default' => 'view',
|
46 |
+
),
|
47 |
+
),
|
48 |
+
),
|
49 |
+
array(
|
50 |
+
'methods' => WP_REST_Server::CREATABLE,
|
51 |
+
'callback' => array( $this, 'create_item' ),
|
52 |
+
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
53 |
+
'args' => array(
|
54 |
+
'key' => array(
|
55 |
+
'required' => true,
|
56 |
+
),
|
57 |
+
'value' => array(),
|
58 |
+
),
|
59 |
+
),
|
60 |
+
) );
|
61 |
+
register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/meta/(?P<id>[\d]+)', array(
|
62 |
+
array(
|
63 |
+
'methods' => WP_REST_Server::READABLE,
|
64 |
+
'callback' => array( $this, 'get_item' ),
|
65 |
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
66 |
+
'args' => array(
|
67 |
+
'context' => array(
|
68 |
+
'default' => 'view',
|
69 |
+
),
|
70 |
+
),
|
71 |
+
),
|
72 |
+
array(
|
73 |
+
'methods' => WP_REST_Server::EDITABLE,
|
74 |
+
'callback' => array( $this, 'update_item' ),
|
75 |
+
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
76 |
+
'args' => array(
|
77 |
+
'key' => array(),
|
78 |
+
'value' => array(),
|
79 |
+
),
|
80 |
+
),
|
81 |
+
array(
|
82 |
+
'methods' => WP_REST_Server::DELETABLE,
|
83 |
+
'callback' => array( $this, 'delete_item' ),
|
84 |
+
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
85 |
+
'args' => array(),
|
86 |
+
),
|
87 |
+
) );
|
88 |
+
register_rest_route( 'wp/v2', $this->parent_base . '/meta/schema', array(
|
89 |
+
'methods' => WP_REST_Server::READABLE,
|
90 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
91 |
+
) );
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Get the meta schema, conforming to JSON Schema
|
96 |
+
*
|
97 |
+
* @return array
|
98 |
+
*/
|
99 |
+
public function get_item_schema() {
|
100 |
+
$schema = array(
|
101 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
102 |
+
'title' => 'meta',
|
103 |
+
'type' => 'object',
|
104 |
+
/*
|
105 |
+
* Base properties for every Post
|
106 |
+
*/
|
107 |
+
'properties' => array(
|
108 |
+
'id' => array(
|
109 |
+
'description' => 'Unique identifier for the object.',
|
110 |
+
'type' => 'int',
|
111 |
+
'context' => array( 'edit' ),
|
112 |
+
),
|
113 |
+
'key' => array(
|
114 |
+
'description' => 'The key for the custom field.',
|
115 |
+
'type' => 'string',
|
116 |
+
'context' => array( 'edit' ),
|
117 |
+
),
|
118 |
+
'value' => array(
|
119 |
+
'description' => 'The value of the custom field.',
|
120 |
+
'type' => 'string',
|
121 |
+
'context' => array( 'edit' ),
|
122 |
+
),
|
123 |
+
),
|
124 |
+
);
|
125 |
+
return $schema;
|
126 |
+
}
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Get the meta ID column for the relevant table.
|
130 |
+
*
|
131 |
+
* @return string
|
132 |
+
*/
|
133 |
+
protected function get_id_column() {
|
134 |
+
return ( 'user' === $this->parent_type ) ? 'umeta_id' : 'meta_id';
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Get the object (parent) ID column for the relevant table.
|
139 |
+
*
|
140 |
+
* @return string
|
141 |
+
*/
|
142 |
+
protected function get_parent_column() {
|
143 |
+
return ( 'user' === $this->parent_type ) ? 'user_id' : 'post_id';
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Retrieve custom fields for object.
|
148 |
+
*
|
149 |
+
* @param WP_REST_Request $request
|
150 |
+
* @return WP_REST_Request|WP_Error List of meta object data on success, WP_Error otherwise
|
151 |
+
*/
|
152 |
+
public function get_items( $request ) {
|
153 |
+
$parent_id = (int) $request['parent_id'];
|
154 |
+
|
155 |
+
global $wpdb;
|
156 |
+
$table = _get_meta_table( $this->parent_type );
|
157 |
+
$parent_column = $this->get_parent_column();
|
158 |
+
$id_column = $this->get_id_column();
|
159 |
+
|
160 |
+
// @codingStandardsIgnoreStart
|
161 |
+
$results = $wpdb->get_results( $wpdb->prepare( "SELECT $id_column, $parent_column, meta_key, meta_value FROM $table WHERE $parent_column = %d", $parent_id ) );
|
162 |
+
// @codingStandardsIgnoreEnd
|
163 |
+
|
164 |
+
$meta = array();
|
165 |
+
|
166 |
+
foreach ( $results as $row ) {
|
167 |
+
$value = $this->prepare_item_for_response( $row, $request, true );
|
168 |
+
|
169 |
+
if ( is_wp_error( $value ) ) {
|
170 |
+
continue;
|
171 |
+
}
|
172 |
+
|
173 |
+
$meta[] = $this->prepare_response_for_collection( $value );
|
174 |
+
}
|
175 |
+
|
176 |
+
return rest_ensure_response( $meta );
|
177 |
+
}
|
178 |
+
|
179 |
+
/**
|
180 |
+
* Retrieve custom field object.
|
181 |
+
*
|
182 |
+
* @param WP_REST_Request $request
|
183 |
+
* @return WP_REST_Request|WP_Error Meta object data on success, WP_Error otherwise
|
184 |
+
*/
|
185 |
+
public function get_item( $request ) {
|
186 |
+
$parent_id = (int) $request['parent_id'];
|
187 |
+
$mid = (int) $request['id'];
|
188 |
+
|
189 |
+
$parent_column = $this->get_parent_column();
|
190 |
+
$meta = get_metadata_by_mid( $this->parent_type, $mid );
|
191 |
+
|
192 |
+
if ( empty( $meta ) ) {
|
193 |
+
return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta ID.' ), array( 'status' => 404 ) );
|
194 |
+
}
|
195 |
+
|
196 |
+
if ( absint( $meta->$parent_column ) !== $parent_id ) {
|
197 |
+
return new WP_Error( 'rest_meta_' . $this->parent_type . '_mismatch', __( 'Meta does not belong to this object' ), array( 'status' => 400 ) );
|
198 |
+
}
|
199 |
+
|
200 |
+
return $this->prepare_item_for_response( $meta, $request );
|
201 |
+
}
|
202 |
+
|
203 |
+
/**
|
204 |
+
* Prepares meta data for return as an object.
|
205 |
+
*
|
206 |
+
* @param stdClass $data Metadata row from database
|
207 |
+
* @param WP_REST_Request $request
|
208 |
+
* @param boolean $is_raw Is the value field still serialized? (False indicates the value has been unserialized)
|
209 |
+
* @return WP_REST_Response|WP_Error Meta object data on success, WP_Error otherwise
|
210 |
+
*/
|
211 |
+
public function prepare_item_for_response( $data, $request, $is_raw = false ) {
|
212 |
+
$id_column = $this->get_id_column();
|
213 |
+
$id = $data->$id_column;
|
214 |
+
$key = $data->meta_key;
|
215 |
+
$value = $data->meta_value;
|
216 |
+
|
217 |
+
// Don't expose protected fields.
|
218 |
+
if ( is_protected_meta( $key ) ) {
|
219 |
+
return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $key ), array( 'status' => 403 ) );
|
220 |
+
}
|
221 |
+
|
222 |
+
// Normalize serialized strings
|
223 |
+
if ( $is_raw && is_serialized_string( $value ) ) {
|
224 |
+
$value = unserialize( $value );
|
225 |
+
}
|
226 |
+
|
227 |
+
// Don't expose serialized data
|
228 |
+
if ( is_serialized( $value ) || ! is_string( $value ) ) {
|
229 |
+
return new WP_Error( 'rest_meta_protected', sprintf( __( '%s contains serialized data.' ), $key ), array( 'status' => 403 ) );
|
230 |
+
}
|
231 |
+
|
232 |
+
$meta = array(
|
233 |
+
'id' => (int) $id,
|
234 |
+
'key' => $key,
|
235 |
+
'value' => $value,
|
236 |
+
);
|
237 |
+
|
238 |
+
$response = rest_ensure_response( $meta );
|
239 |
+
$parent_column = $this->get_parent_column();
|
240 |
+
$response->add_link( 'about', rest_url( 'wp/' . $this->parent_base . '/' . $data->$parent_column ), array( 'embeddable' => true ) );
|
241 |
+
|
242 |
+
return apply_filters( 'rest_prepare_meta_value', $response, $request );
|
243 |
+
}
|
244 |
+
|
245 |
+
/**
|
246 |
+
* Add meta to an object.
|
247 |
+
*
|
248 |
+
* @param WP_REST_Request $request
|
249 |
+
* @return WP_REST_Response|WP_Error
|
250 |
+
*/
|
251 |
+
public function update_item( $request ) {
|
252 |
+
$parent_id = (int) $request['parent_id'];
|
253 |
+
$mid = (int) $request['id'];
|
254 |
+
|
255 |
+
$parent_column = $this->get_parent_column();
|
256 |
+
$current = get_metadata_by_mid( $this->parent_type, $mid );
|
257 |
+
|
258 |
+
if ( empty( $current ) ) {
|
259 |
+
return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta ID.' ), array( 'status' => 404 ) );
|
260 |
+
}
|
261 |
+
|
262 |
+
if ( absint( $current->$parent_column ) !== $parent_id ) {
|
263 |
+
return new WP_Error( 'rest_meta_' . $this->parent_type . '_mismatch', __( 'Meta does not belong to this object' ), array( 'status' => 400 ) );
|
264 |
+
}
|
265 |
+
|
266 |
+
if ( ! isset( $request['key'] ) && ! isset( $request['value'] ) ) {
|
267 |
+
return new WP_Error( 'rest_meta_data_invalid', __( 'Invalid meta parameters.' ), array( 'status' => 400 ) );
|
268 |
+
}
|
269 |
+
if ( isset( $request['key'] ) ) {
|
270 |
+
$key = $request['key'];
|
271 |
+
} else {
|
272 |
+
$key = $current->meta_key;
|
273 |
+
}
|
274 |
+
|
275 |
+
if ( isset( $request['value'] ) ) {
|
276 |
+
$value = $request['value'];
|
277 |
+
} else {
|
278 |
+
$value = $current->meta_value;
|
279 |
+
}
|
280 |
+
|
281 |
+
if ( ! $key ) {
|
282 |
+
return new WP_Error( 'rest_meta_invalid_key', __( 'Invalid meta key.' ), array( 'status' => 400 ) );
|
283 |
+
}
|
284 |
+
|
285 |
+
// for now let's not allow updating of arrays, objects or serialized values.
|
286 |
+
if ( ! $this->is_valid_meta_data( $current->meta_value ) ) {
|
287 |
+
$code = ( $this->parent_type === 'post' ) ? 'rest_post_invalid_action' : 'rest_meta_invalid_action';
|
288 |
+
return new WP_Error( $code, __( 'Invalid existing meta data for action.' ), array( 'status' => 400 ) );
|
289 |
+
}
|
290 |
+
|
291 |
+
if ( ! $this->is_valid_meta_data( $value ) ) {
|
292 |
+
$code = ( $this->parent_type === 'post' ) ? 'rest_post_invalid_action' : 'rest_meta_invalid_action';
|
293 |
+
return new WP_Error( $code, __( 'Invalid provided meta data for action.' ), array( 'status' => 400 ) );
|
294 |
+
}
|
295 |
+
|
296 |
+
if ( is_protected_meta( $current->meta_key ) ) {
|
297 |
+
return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $current->meta_key ), array( 'status' => 403 ) );
|
298 |
+
}
|
299 |
+
|
300 |
+
if ( is_protected_meta( $key ) ) {
|
301 |
+
return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $key ), array( 'status' => 403 ) );
|
302 |
+
}
|
303 |
+
|
304 |
+
// update_metadata_by_mid will return false if these are equal, so check
|
305 |
+
// first and pass through
|
306 |
+
if ( (string) $value === $current->meta_value && (string) $key === $current->meta_key ) {
|
307 |
+
return $this->get_item( $request );
|
308 |
+
}
|
309 |
+
|
310 |
+
if ( ! update_metadata_by_mid( $this->parent_type, $mid, $value, $key ) ) {
|
311 |
+
return new WP_Error( 'rest_meta_could_not_update', __( 'Could not update meta.' ), array( 'status' => 500 ) );
|
312 |
+
}
|
313 |
+
|
314 |
+
$request = new WP_REST_Request( 'GET' );
|
315 |
+
$request->set_query_params( array(
|
316 |
+
'context' => 'edit',
|
317 |
+
'parent_id' => $parent_id,
|
318 |
+
'id' => $mid,
|
319 |
+
) );
|
320 |
+
$response = $this->get_item( $request );
|
321 |
+
|
322 |
+
return rest_ensure_response( $response );
|
323 |
+
}
|
324 |
+
|
325 |
+
/**
|
326 |
+
* Check if the data provided is valid data.
|
327 |
+
*
|
328 |
+
* Excludes serialized data from being sent via the API.
|
329 |
+
*
|
330 |
+
* @see https://github.com/WP-API/WP-API/pull/68
|
331 |
+
* @param mixed $data Data to be checked
|
332 |
+
* @return boolean Whether the data is valid or not
|
333 |
+
*/
|
334 |
+
protected function is_valid_meta_data( $data ) {
|
335 |
+
if ( is_array( $data ) || is_object( $data ) || is_serialized( $data ) ) {
|
336 |
+
return false;
|
337 |
+
}
|
338 |
+
|
339 |
+
return true;
|
340 |
+
}
|
341 |
+
|
342 |
+
/**
|
343 |
+
* Add meta to an object.
|
344 |
+
*
|
345 |
+
* @param WP_REST_Request $request
|
346 |
+
* @return WP_REST_Response|WP_Error
|
347 |
+
*/
|
348 |
+
public function create_item( $request ) {
|
349 |
+
$parent_id = (int) $request['parent_id'];
|
350 |
+
|
351 |
+
if ( ! $this->is_valid_meta_data( $request['value'] ) ) {
|
352 |
+
$code = ( $this->parent_type === 'post' ) ? 'rest_post_invalid_action' : 'rest_meta_invalid_action';
|
353 |
+
|
354 |
+
// for now let's not allow updating of arrays, objects or serialized values.
|
355 |
+
return new WP_Error( $code, __( 'Invalid provided meta data for action.' ), array( 'status' => 400 ) );
|
356 |
+
}
|
357 |
+
|
358 |
+
if ( is_protected_meta( $request['key'] ) ) {
|
359 |
+
return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $request['key'] ), array( 'status' => 403 ) );
|
360 |
+
}
|
361 |
+
|
362 |
+
if ( empty( $request['key'] ) ) {
|
363 |
+
return new WP_Error( 'rest_meta_invalid_key', __( 'Invalid meta key.' ), array( 'status' => 400 ) );
|
364 |
+
}
|
365 |
+
|
366 |
+
$meta_key = wp_slash( $request['key'] );
|
367 |
+
$value = wp_slash( $request['value'] );
|
368 |
+
|
369 |
+
$mid = add_metadata( $this->parent_type, $parent_id, $meta_key, $value );
|
370 |
+
if ( ! $mid ) {
|
371 |
+
return new WP_Error( 'rest_meta_could_not_add', __( 'Could not add meta.' ), array( 'status' => 400 ) );
|
372 |
+
}
|
373 |
+
|
374 |
+
$request = new WP_REST_Request( 'GET' );
|
375 |
+
$request->set_query_params( array(
|
376 |
+
'context' => 'edit',
|
377 |
+
'parent_id' => $parent_id,
|
378 |
+
'id' => $mid,
|
379 |
+
) );
|
380 |
+
$response = rest_ensure_response( $this->get_item( $request ) );
|
381 |
+
|
382 |
+
$response->set_status( 201 );
|
383 |
+
$data = $response->get_data();
|
384 |
+
$response->header( 'Location', rest_url( $this->parent_base . '/' . $parent_id . '/meta/' . $data['id'] ) );
|
385 |
+
|
386 |
+
return $response;
|
387 |
+
}
|
388 |
+
|
389 |
+
/**
|
390 |
+
* Delete meta from an object.
|
391 |
+
*
|
392 |
+
* @param WP_REST_Request $request
|
393 |
+
* @return WP_REST_Response|WP_Error Message on success, WP_Error otherwise
|
394 |
+
*/
|
395 |
+
public function delete_item( $request ) {
|
396 |
+
$parent_id = (int) $request['parent_id'];
|
397 |
+
$mid = (int) $request['id'];
|
398 |
+
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
|
399 |
+
|
400 |
+
// We don't support trashing for this type, error out
|
401 |
+
if ( ! $force ) {
|
402 |
+
return new WP_Error( 'rest_trash_not_supported', __( 'Meta does not support trashing.' ), array( 'status' => 501 ) );
|
403 |
+
}
|
404 |
+
|
405 |
+
$parent_column = $this->get_parent_column();
|
406 |
+
$current = get_metadata_by_mid( $this->parent_type, $mid );
|
407 |
+
|
408 |
+
if ( empty( $current ) ) {
|
409 |
+
return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta ID.' ), array( 'status' => 404 ) );
|
410 |
+
}
|
411 |
+
|
412 |
+
if ( absint( $current->$parent_column ) !== (int) $parent_id ) {
|
413 |
+
return new WP_Error( 'rest_meta_' . $this->parent_type . '_mismatch', __( 'Meta does not belong to this object' ), array( 'status' => 400 ) );
|
414 |
+
}
|
415 |
+
|
416 |
+
// for now let's not allow updating of arrays, objects or serialized values.
|
417 |
+
if ( ! $this->is_valid_meta_data( $current->meta_value ) ) {
|
418 |
+
$code = ( $this->parent_type === 'post' ) ? 'rest_post_invalid_action' : 'rest_meta_invalid_action';
|
419 |
+
return new WP_Error( $code, __( 'Invalid existing meta data for action.' ), array( 'status' => 400 ) );
|
420 |
+
}
|
421 |
+
|
422 |
+
if ( is_protected_meta( $current->meta_key ) ) {
|
423 |
+
return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $current->meta_key ), array( 'status' => 403 ) );
|
424 |
+
}
|
425 |
+
|
426 |
+
if ( ! delete_metadata_by_mid( $this->parent_type, $mid ) ) {
|
427 |
+
return new WP_Error( 'rest_meta_could_not_delete', __( 'Could not delete meta.' ), array( 'status' => 500 ) );
|
428 |
+
}
|
429 |
+
|
430 |
+
return rest_ensure_response( array( 'message' => __( 'Deleted meta' ) ) );
|
431 |
+
}
|
432 |
+
}
|
lib/endpoints/class-wp-rest-meta-posts-controller.php
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_REST_Meta_Posts_Controller extends WP_REST_Meta_Controller {
|
4 |
+
/**
|
5 |
+
* Associated object type.
|
6 |
+
*
|
7 |
+
* @var string Type slug ("post" or "user")
|
8 |
+
*/
|
9 |
+
protected $parent_type = 'post';
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Associated post type name.
|
13 |
+
*
|
14 |
+
* @var string
|
15 |
+
*/
|
16 |
+
protected $parent_post_type;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Associated post type controller class object.
|
20 |
+
*
|
21 |
+
* @var WP_REST_Posts_Controller
|
22 |
+
*/
|
23 |
+
protected $parent_controller;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Base path for post type endpoints.
|
27 |
+
*
|
28 |
+
* @var string
|
29 |
+
*/
|
30 |
+
protected $parent_base;
|
31 |
+
|
32 |
+
public function __construct( $parent_post_type ) {
|
33 |
+
$this->parent_post_type = $parent_post_type;
|
34 |
+
$this->parent_controller = new WP_REST_Posts_Controller( $this->parent_post_type );
|
35 |
+
$this->parent_base = $this->parent_controller->get_post_type_base( $this->parent_post_type );
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Check if a given request has access to get meta for a post.
|
40 |
+
*
|
41 |
+
* @param WP_REST_Request $request Full data about the request.
|
42 |
+
* @return WP_Error|boolean
|
43 |
+
*/
|
44 |
+
public function get_items_permissions_check( $request ) {
|
45 |
+
$parent = get_post( (int) $request['parent_id'] );
|
46 |
+
|
47 |
+
if ( empty( $parent ) || empty( $parent->ID ) ) {
|
48 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
|
49 |
+
}
|
50 |
+
|
51 |
+
if ( ! $this->parent_controller->check_read_permission( $parent ) ) {
|
52 |
+
return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => 403 ) );
|
53 |
+
}
|
54 |
+
|
55 |
+
$post_type = get_post_type_object( $parent->post_type );
|
56 |
+
if ( ! current_user_can( $post_type->cap->edit_post, $parent->ID ) ) {
|
57 |
+
return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view the meta for this post.' ), array( 'status' => 403 ) );
|
58 |
+
}
|
59 |
+
return true;
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Check if a given request has access to get a specific meta entry for a post.
|
64 |
+
*
|
65 |
+
* @param WP_REST_Request $request Full data about the request.
|
66 |
+
* @return WP_Error|boolean
|
67 |
+
*/
|
68 |
+
public function get_item_permissions_check( $request ) {
|
69 |
+
return $this->get_items_permissions_check( $request );
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Check if a given request has access to create a meta entry for a post.
|
74 |
+
*
|
75 |
+
* @param WP_REST_Request $request Full data about the request.
|
76 |
+
* @return WP_Error|boolean
|
77 |
+
*/
|
78 |
+
public function create_item_permissions_check( $request ) {
|
79 |
+
return $this->get_items_permissions_check( $request );
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Check if a given request has access to update a meta entry for a post.
|
84 |
+
*
|
85 |
+
* @param WP_REST_Request $request Full data about the request.
|
86 |
+
* @return WP_Error|boolean
|
87 |
+
*/
|
88 |
+
public function update_item_permissions_check( $request ) {
|
89 |
+
return $this->get_items_permissions_check( $request );
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Check if a given request has access to delete meta for a post.
|
94 |
+
*
|
95 |
+
* @param WP_REST_Request $request Full details about the request.
|
96 |
+
* @return WP_Error|boolean
|
97 |
+
*/
|
98 |
+
public function delete_item_permissions_check( $request ) {
|
99 |
+
$parent = get_post( (int) $request['parent_id'] );
|
100 |
+
|
101 |
+
if ( empty( $parent ) || empty( $parent->ID ) ) {
|
102 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
|
103 |
+
}
|
104 |
+
|
105 |
+
if ( ! $this->parent_controller->check_read_permission( $parent ) ) {
|
106 |
+
return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => 403 ) );
|
107 |
+
}
|
108 |
+
|
109 |
+
$post_type = get_post_type_object( $parent->post_type );
|
110 |
+
if ( ! current_user_can( $post_type->cap->delete_post, $parent->ID ) ) {
|
111 |
+
return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot delete the meta for this post.' ), array( 'status' => 403 ) );
|
112 |
+
}
|
113 |
+
return true;
|
114 |
+
}
|
115 |
+
}
|
lib/endpoints/class-wp-rest-post-statuses-controller.php
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Register the routes for the objects of the controller.
|
7 |
+
*/
|
8 |
+
public function register_routes() {
|
9 |
+
|
10 |
+
register_rest_route( 'wp/v2', '/statuses', array(
|
11 |
+
'methods' => WP_REST_Server::READABLE,
|
12 |
+
'callback' => array( $this, 'get_items' ),
|
13 |
+
) );
|
14 |
+
|
15 |
+
register_rest_route( 'wp/v2', '/statuses/schema', array(
|
16 |
+
'methods' => WP_REST_Server::READABLE,
|
17 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
18 |
+
) );
|
19 |
+
|
20 |
+
register_rest_route( 'wp/v2', '/statuses/(?P<status>[\w-]+)', array(
|
21 |
+
'methods' => WP_REST_Server::READABLE,
|
22 |
+
'callback' => array( $this, 'get_item' ),
|
23 |
+
) );
|
24 |
+
}
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Get all post statuses, depending on user context
|
28 |
+
*
|
29 |
+
* @param WP_REST_Request $request
|
30 |
+
* @return array|WP_Error
|
31 |
+
*/
|
32 |
+
public function get_items( $request ) {
|
33 |
+
$data = array();
|
34 |
+
if ( is_user_logged_in() ) {
|
35 |
+
$statuses = get_post_stati( array( 'internal' => false ), 'object' );
|
36 |
+
} else {
|
37 |
+
$statuses = get_post_stati( array( 'public' => true ), 'object' );
|
38 |
+
}
|
39 |
+
foreach ( $statuses as $obj ) {
|
40 |
+
$status = $this->prepare_item_for_response( $obj, $request );
|
41 |
+
if ( is_wp_error( $status ) ) {
|
42 |
+
continue;
|
43 |
+
}
|
44 |
+
$data[ $obj->name ] = $this->prepare_response_for_collection( $status );
|
45 |
+
}
|
46 |
+
return $data;
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Get a specific post status
|
51 |
+
*
|
52 |
+
* @param WP_REST_Request $request
|
53 |
+
* @return array|WP_Error
|
54 |
+
*/
|
55 |
+
public function get_item( $request ) {
|
56 |
+
$obj = get_post_status_object( $request['status'] );
|
57 |
+
if ( empty( $obj ) ) {
|
58 |
+
return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) );
|
59 |
+
}
|
60 |
+
return $this->prepare_item_for_response( $obj, $request );
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Prepare a post status object for serialization
|
65 |
+
*
|
66 |
+
* @param stdClass $status Post status data
|
67 |
+
* @param WP_REST_Request $request
|
68 |
+
* @return WP_REST_Response Post status data
|
69 |
+
*/
|
70 |
+
public function prepare_item_for_response( $status, $request ) {
|
71 |
+
if ( ( false === $status->public && ! is_user_logged_in() ) || ( true === $status->internal && is_user_logged_in() ) ) {
|
72 |
+
return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view status.' ), array( 'status' => 403 ) );
|
73 |
+
}
|
74 |
+
|
75 |
+
$data = array(
|
76 |
+
'name' => $status->label,
|
77 |
+
'private' => (bool) $status->private,
|
78 |
+
'protected' => (bool) $status->protected,
|
79 |
+
'public' => (bool) $status->public,
|
80 |
+
'queryable' => (bool) $status->publicly_queryable,
|
81 |
+
'show_in_list' => (bool) $status->show_in_admin_all_list,
|
82 |
+
'slug' => $status->name,
|
83 |
+
);
|
84 |
+
|
85 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
86 |
+
$data = $this->filter_response_by_context( $data, $context );
|
87 |
+
$data = $this->add_additional_fields_to_object( $data, $request );
|
88 |
+
|
89 |
+
$data = rest_ensure_response( $data );
|
90 |
+
|
91 |
+
$posts_controller = new WP_REST_Posts_Controller( 'post' );
|
92 |
+
|
93 |
+
if ( 'publish' === $status->name ) {
|
94 |
+
$data->add_link( 'archives', rest_url( '/wp/v2/' . $posts_controller->get_post_type_base( 'post' ) ) );
|
95 |
+
} else {
|
96 |
+
$data->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( '/wp/v2/' . $posts_controller->get_post_type_base( 'post' ) ) ) );
|
97 |
+
}
|
98 |
+
|
99 |
+
return $data;
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Get the Post status' schema, conforming to JSON Schema
|
104 |
+
*
|
105 |
+
* @return array
|
106 |
+
*/
|
107 |
+
public function get_item_schema() {
|
108 |
+
$schema = array(
|
109 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
110 |
+
'title' => 'status',
|
111 |
+
'type' => 'object',
|
112 |
+
'properties' => array(
|
113 |
+
'name' => array(
|
114 |
+
'description' => 'The title for the status.',
|
115 |
+
'type' => 'string',
|
116 |
+
'context' => array( 'view' ),
|
117 |
+
),
|
118 |
+
'private' => array(
|
119 |
+
'description' => 'Whether posts with this status should be private.',
|
120 |
+
'type' => 'boolean',
|
121 |
+
'context' => array( 'view' ),
|
122 |
+
),
|
123 |
+
'protected' => array(
|
124 |
+
'description' => 'Whether posts with this status should be protected.',
|
125 |
+
'type' => 'boolean',
|
126 |
+
'context' => array( 'view' ),
|
127 |
+
),
|
128 |
+
'public' => array(
|
129 |
+
'description' => 'Whether posts of this status should be shown in the front end of the site.',
|
130 |
+
'type' => 'boolean',
|
131 |
+
'context' => array( 'view' ),
|
132 |
+
),
|
133 |
+
'queryable' => array(
|
134 |
+
'description' => 'Whether posts with this status should be publicly-queryable.',
|
135 |
+
'type' => 'boolean',
|
136 |
+
'context' => array( 'view' ),
|
137 |
+
),
|
138 |
+
'show_in_list' => array(
|
139 |
+
'description' => 'Whether to include posts in the edit listing for their post type.',
|
140 |
+
'type' => 'boolean',
|
141 |
+
'context' => array( 'view' ),
|
142 |
+
),
|
143 |
+
'slug' => array(
|
144 |
+
'description' => 'An alphanumeric identifier for the status.',
|
145 |
+
'type' => 'string',
|
146 |
+
'context' => array( 'view' ),
|
147 |
+
),
|
148 |
+
),
|
149 |
+
);
|
150 |
+
return $this->add_additional_fields_schema( $schema );
|
151 |
+
}
|
152 |
+
|
153 |
+
}
|
lib/endpoints/class-wp-rest-post-types-controller.php
ADDED
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_REST_Post_Types_Controller extends WP_REST_Controller {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Register the routes for the objects of the controller.
|
7 |
+
*/
|
8 |
+
public function register_routes() {
|
9 |
+
|
10 |
+
register_rest_route( 'wp/v2', '/types', array(
|
11 |
+
'methods' => WP_REST_Server::READABLE,
|
12 |
+
'callback' => array( $this, 'get_items' ),
|
13 |
+
'args' => array(
|
14 |
+
'post_type' => array(
|
15 |
+
'sanitize_callback' => 'sanitize_key',
|
16 |
+
),
|
17 |
+
),
|
18 |
+
) );
|
19 |
+
|
20 |
+
register_rest_route( 'wp/v2', '/types/schema', array(
|
21 |
+
'methods' => WP_REST_Server::READABLE,
|
22 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
23 |
+
) );
|
24 |
+
|
25 |
+
register_rest_route( 'wp/v2', '/types/(?P<type>[\w-]+)', array(
|
26 |
+
'methods' => WP_REST_Server::READABLE,
|
27 |
+
'callback' => array( $this, 'get_item' ),
|
28 |
+
) );
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Get all public post types
|
33 |
+
*
|
34 |
+
* @param WP_REST_Request $request
|
35 |
+
* @return array|WP_Error
|
36 |
+
*/
|
37 |
+
public function get_items( $request ) {
|
38 |
+
$data = array();
|
39 |
+
foreach ( get_post_types( array( 'public' => true ), 'object' ) as $obj ) {
|
40 |
+
$post_type = $this->prepare_item_for_response( $obj, $request );
|
41 |
+
if ( is_wp_error( $post_type ) ) {
|
42 |
+
continue;
|
43 |
+
}
|
44 |
+
$data[ $obj->name ] = $post_type;
|
45 |
+
}
|
46 |
+
return $data;
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Get a specific post type
|
51 |
+
*
|
52 |
+
* @param WP_REST_Request $request
|
53 |
+
* @return array|WP_Error
|
54 |
+
*/
|
55 |
+
public function get_item( $request ) {
|
56 |
+
$obj = get_post_type_object( $request['type'] );
|
57 |
+
if ( empty( $obj ) ) {
|
58 |
+
return new WP_Error( 'rest_type_invalid', __( 'Invalid type.' ), array( 'status' => 404 ) );
|
59 |
+
}
|
60 |
+
return $this->prepare_item_for_response( $obj, $request );
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Prepare a post type object for serialization
|
65 |
+
*
|
66 |
+
* @param stdClass $post_type Post type data
|
67 |
+
* @param WP_REST_Request $request
|
68 |
+
* @return array Post type data
|
69 |
+
*/
|
70 |
+
public function prepare_item_for_response( $post_type, $request ) {
|
71 |
+
if ( false === $post_type->public ) {
|
72 |
+
return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view type.' ), array( 'status' => 403 ) );
|
73 |
+
}
|
74 |
+
|
75 |
+
$data = array(
|
76 |
+
'description' => $post_type->description,
|
77 |
+
'hierarchical' => $post_type->hierarchical,
|
78 |
+
'labels' => $post_type->labels,
|
79 |
+
'name' => $post_type->label,
|
80 |
+
'slug' => $post_type->name,
|
81 |
+
);
|
82 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
83 |
+
$data = $this->filter_response_by_context( $data, $context );
|
84 |
+
$data = $this->add_additional_fields_to_object( $data, $request );
|
85 |
+
|
86 |
+
return $data;
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Get the Post type's schema, conforming to JSON Schema
|
91 |
+
*
|
92 |
+
* @return array
|
93 |
+
*/
|
94 |
+
public function get_item_schema() {
|
95 |
+
$schema = array(
|
96 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
97 |
+
'title' => 'type',
|
98 |
+
'type' => 'object',
|
99 |
+
'properties' => array(
|
100 |
+
'description' => array(
|
101 |
+
'description' => 'A human-readable description of the object.',
|
102 |
+
'type' => 'string',
|
103 |
+
'context' => array( 'view' ),
|
104 |
+
),
|
105 |
+
'hierarchical' => array(
|
106 |
+
'description' => 'Whether or not the type should have children.',
|
107 |
+
'type' => 'boolean',
|
108 |
+
'context' => array( 'view' ),
|
109 |
+
),
|
110 |
+
'labels' => array(
|
111 |
+
'description' => 'Human-readable labels for the type for various contexts.',
|
112 |
+
'type' => 'object',
|
113 |
+
'context' => array( 'view' ),
|
114 |
+
),
|
115 |
+
'name' => array(
|
116 |
+
'description' => 'The title for the object.',
|
117 |
+
'type' => 'string',
|
118 |
+
'context' => array( 'view' ),
|
119 |
+
),
|
120 |
+
'slug' => array(
|
121 |
+
'description' => 'An alphanumeric identifier for the object.',
|
122 |
+
'type' => 'string',
|
123 |
+
'context' => array( 'view' ),
|
124 |
+
),
|
125 |
+
),
|
126 |
+
);
|
127 |
+
return $this->add_additional_fields_schema( $schema );
|
128 |
+
}
|
129 |
+
|
130 |
+
}
|
lib/endpoints/class-wp-rest-posts-controller.php
ADDED
@@ -0,0 +1,1454 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_REST_Posts_Controller extends WP_REST_Controller {
|
4 |
+
|
5 |
+
protected $post_type;
|
6 |
+
|
7 |
+
public function __construct( $post_type ) {
|
8 |
+
$this->post_type = $post_type;
|
9 |
+
}
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Register the routes for the objects of the controller.
|
13 |
+
*/
|
14 |
+
public function register_routes() {
|
15 |
+
|
16 |
+
$base = $this->get_post_type_base( $this->post_type );
|
17 |
+
|
18 |
+
$posts_args = array(
|
19 |
+
'context' => array(
|
20 |
+
'default' => 'view',
|
21 |
+
),
|
22 |
+
'page' => array(
|
23 |
+
'default' => 1,
|
24 |
+
'sanitize_callback' => 'absint',
|
25 |
+
),
|
26 |
+
'per_page' => array(
|
27 |
+
'default' => 10,
|
28 |
+
'sanitize_callback' => 'absint',
|
29 |
+
),
|
30 |
+
);
|
31 |
+
|
32 |
+
foreach ( $this->get_allowed_query_vars() as $var ) {
|
33 |
+
if ( ! isset( $posts_args[ $var ] ) ) {
|
34 |
+
$posts_args[ $var ] = array();
|
35 |
+
}
|
36 |
+
}
|
37 |
+
|
38 |
+
register_rest_route( 'wp/v2', '/' . $base, array(
|
39 |
+
array(
|
40 |
+
'methods' => WP_REST_Server::READABLE,
|
41 |
+
'callback' => array( $this, 'get_items' ),
|
42 |
+
'args' => $posts_args,
|
43 |
+
),
|
44 |
+
array(
|
45 |
+
'methods' => WP_REST_Server::CREATABLE,
|
46 |
+
'callback' => array( $this, 'create_item' ),
|
47 |
+
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
48 |
+
'args' => $this->get_endpoint_args_for_item_schema( true ),
|
49 |
+
),
|
50 |
+
) );
|
51 |
+
register_rest_route( 'wp/v2', '/' . $base . '/(?P<id>[\d]+)', array(
|
52 |
+
array(
|
53 |
+
'methods' => WP_REST_Server::READABLE,
|
54 |
+
'callback' => array( $this, 'get_item' ),
|
55 |
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
56 |
+
'args' => array(
|
57 |
+
'context' => array(
|
58 |
+
'default' => 'view',
|
59 |
+
),
|
60 |
+
),
|
61 |
+
),
|
62 |
+
array(
|
63 |
+
'methods' => WP_REST_Server::EDITABLE,
|
64 |
+
'callback' => array( $this, 'update_item' ),
|
65 |
+
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
66 |
+
'args' => $this->get_endpoint_args_for_item_schema( false ),
|
67 |
+
),
|
68 |
+
array(
|
69 |
+
'methods' => WP_REST_Server::DELETABLE,
|
70 |
+
'callback' => array( $this, 'delete_item' ),
|
71 |
+
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
72 |
+
'args' => array(
|
73 |
+
'force' => array(
|
74 |
+
'default' => false,
|
75 |
+
),
|
76 |
+
),
|
77 |
+
),
|
78 |
+
) );
|
79 |
+
register_rest_route( 'wp/v2', '/' . $base . '/schema', array(
|
80 |
+
'methods' => WP_REST_Server::READABLE,
|
81 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
82 |
+
) );
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Get a collection of posts
|
87 |
+
*
|
88 |
+
* @param WP_REST_Request $request Full details about the request
|
89 |
+
* @return WP_Error|WP_REST_Response
|
90 |
+
*/
|
91 |
+
public function get_items( $request ) {
|
92 |
+
$args = (array) $request->get_params();
|
93 |
+
$args['post_type'] = $this->post_type;
|
94 |
+
$args['paged'] = $args['page'];
|
95 |
+
$args['posts_per_page'] = $args['per_page'];
|
96 |
+
unset( $args['page'] );
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Alter the query arguments for a request.
|
100 |
+
*
|
101 |
+
* This allows you to set extra arguments or defaults for a post
|
102 |
+
* collection request.
|
103 |
+
*
|
104 |
+
* @param array $args Map of query var to query value.
|
105 |
+
* @param WP_REST_Request $request Full details about the request.
|
106 |
+
*/
|
107 |
+
$args = apply_filters( 'rest_post_query', $args, $request );
|
108 |
+
$query_args = $this->prepare_items_query( $args );
|
109 |
+
|
110 |
+
$posts_query = new WP_Query();
|
111 |
+
$query_result = $posts_query->query( $query_args );
|
112 |
+
|
113 |
+
$posts = array();
|
114 |
+
foreach ( $query_result as $post ) {
|
115 |
+
if ( ! $this->check_read_permission( $post ) ) {
|
116 |
+
continue;
|
117 |
+
}
|
118 |
+
|
119 |
+
$data = $this->prepare_item_for_response( $post, $request );
|
120 |
+
$posts[] = $this->prepare_response_for_collection( $data );
|
121 |
+
}
|
122 |
+
|
123 |
+
$response = rest_ensure_response( $posts );
|
124 |
+
$count_query = new WP_Query();
|
125 |
+
unset( $query_args['paged'] );
|
126 |
+
$query_result = $count_query->query( $query_args );
|
127 |
+
$total_posts = $count_query->found_posts;
|
128 |
+
$response->header( 'X-WP-Total', (int) $total_posts );
|
129 |
+
$max_pages = ceil( $total_posts / $request['per_page'] );
|
130 |
+
$response->header( 'X-WP-TotalPages', (int) $max_pages );
|
131 |
+
|
132 |
+
$base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/' . $this->get_post_type_base( $this->post_type ) ) );
|
133 |
+
if ( $request['page'] > 1 ) {
|
134 |
+
$prev_page = $request['page'] - 1;
|
135 |
+
if ( $prev_page > $max_pages ) {
|
136 |
+
$prev_page = $max_pages;
|
137 |
+
}
|
138 |
+
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
139 |
+
$response->link_header( 'prev', $prev_link );
|
140 |
+
}
|
141 |
+
if ( $max_pages > $request['page'] ) {
|
142 |
+
$next_page = $request['page'] + 1;
|
143 |
+
$next_link = add_query_arg( 'page', $next_page, $base );
|
144 |
+
$response->link_header( 'next', $next_link );
|
145 |
+
}
|
146 |
+
|
147 |
+
return $response;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Get a single post
|
152 |
+
*
|
153 |
+
* @param WP_REST_Request $request Full details about the request
|
154 |
+
* @return WP_Error|WP_REST_Response
|
155 |
+
*/
|
156 |
+
public function get_item( $request ) {
|
157 |
+
$id = (int) $request['id'];
|
158 |
+
$post = get_post( $id );
|
159 |
+
|
160 |
+
if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
|
161 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
|
162 |
+
}
|
163 |
+
|
164 |
+
$data = $this->prepare_item_for_response( $post, $request );
|
165 |
+
$response = rest_ensure_response( $data );
|
166 |
+
|
167 |
+
$response->link_header( 'alternate', get_permalink( $id ), array( 'type' => 'text/html' ) );
|
168 |
+
|
169 |
+
return $response;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* Create a single post
|
174 |
+
*
|
175 |
+
* @param WP_REST_Request $request Full details about the request
|
176 |
+
* @return WP_Error|WP_REST_Response
|
177 |
+
*/
|
178 |
+
public function create_item( $request ) {
|
179 |
+
if ( ! empty( $request['id'] ) ) {
|
180 |
+
return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) );
|
181 |
+
}
|
182 |
+
|
183 |
+
$post = $this->prepare_item_for_database( $request );
|
184 |
+
if ( is_wp_error( $post ) ) {
|
185 |
+
return $post;
|
186 |
+
}
|
187 |
+
|
188 |
+
$post->post_type = $this->post_type;
|
189 |
+
$post_id = wp_insert_post( $post, true );
|
190 |
+
|
191 |
+
if ( is_wp_error( $post_id ) ) {
|
192 |
+
|
193 |
+
if ( in_array( $post_id->get_error_code(), array( 'db_insert_error' ) ) ) {
|
194 |
+
$post_id->add_data( array( 'status' => 500 ) );
|
195 |
+
} else {
|
196 |
+
$post_id->add_data( array( 'status' => 400 ) );
|
197 |
+
}
|
198 |
+
return $post_id;
|
199 |
+
}
|
200 |
+
$post->ID = $post_id;
|
201 |
+
|
202 |
+
$schema = $this->get_item_schema();
|
203 |
+
|
204 |
+
if ( ! empty( $schema['properties']['sticky'] ) ) {
|
205 |
+
if ( ! empty( $request['sticky'] ) ) {
|
206 |
+
stick_post( $post_id );
|
207 |
+
} else {
|
208 |
+
unstick_post( $post_id );
|
209 |
+
}
|
210 |
+
}
|
211 |
+
|
212 |
+
if ( ! empty( $schema['properties']['featured_image'] ) && isset( $request['featured_image'] ) ) {
|
213 |
+
$this->handle_featured_image( $request['featured_image'], $post->ID );
|
214 |
+
}
|
215 |
+
|
216 |
+
if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
|
217 |
+
set_post_format( $post, $request['format'] );
|
218 |
+
}
|
219 |
+
|
220 |
+
if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
|
221 |
+
$this->handle_template( $request['template'], $post->ID );
|
222 |
+
}
|
223 |
+
|
224 |
+
$this->update_additional_fields_for_object( get_post( $post_id ), $request );
|
225 |
+
|
226 |
+
/**
|
227 |
+
* @TODO: Enable rest_insert_post() action after
|
228 |
+
* Media Controller has been migrated to new style.
|
229 |
+
*
|
230 |
+
* do_action( 'rest_insert_post', $post, $request, true );
|
231 |
+
*/
|
232 |
+
|
233 |
+
$response = $this->get_item( array(
|
234 |
+
'id' => $post_id,
|
235 |
+
'context' => 'edit',
|
236 |
+
) );
|
237 |
+
$response = rest_ensure_response( $response );
|
238 |
+
$response->set_status( 201 );
|
239 |
+
$response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $post->post_type ) . '/' . $post_id ) );
|
240 |
+
|
241 |
+
return $response;
|
242 |
+
}
|
243 |
+
|
244 |
+
/**
|
245 |
+
* Update a single post
|
246 |
+
*
|
247 |
+
* @param WP_REST_Request $request Full details about the request
|
248 |
+
* @return WP_Error|WP_REST_Response
|
249 |
+
*/
|
250 |
+
public function update_item( $request ) {
|
251 |
+
$id = (int) $request['id'];
|
252 |
+
$post = get_post( $id );
|
253 |
+
|
254 |
+
if ( ! $post ) {
|
255 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Post ID is invalid.' ), array( 'status' => 400 ) );
|
256 |
+
}
|
257 |
+
|
258 |
+
$post = $this->prepare_item_for_database( $request );
|
259 |
+
if ( is_wp_error( $post ) ) {
|
260 |
+
return $post;
|
261 |
+
}
|
262 |
+
|
263 |
+
$post_id = wp_update_post( $post, true );
|
264 |
+
if ( is_wp_error( $post_id ) ) {
|
265 |
+
if ( in_array( $post_id->get_error_code(), array( 'db_update_error' ) ) ) {
|
266 |
+
$post_id->add_data( array( 'status' => 500 ) );
|
267 |
+
} else {
|
268 |
+
$post_id->add_data( array( 'status' => 400 ) );
|
269 |
+
}
|
270 |
+
return $post_id;
|
271 |
+
}
|
272 |
+
|
273 |
+
$schema = $this->get_item_schema();
|
274 |
+
|
275 |
+
if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
|
276 |
+
set_post_format( $post, $request['format'] );
|
277 |
+
}
|
278 |
+
|
279 |
+
if ( ! empty( $schema['properties']['featured_image'] ) && isset( $request['featured_image'] ) ) {
|
280 |
+
$this->handle_featured_image( $request['featured_image'], $post_id );
|
281 |
+
}
|
282 |
+
|
283 |
+
if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) {
|
284 |
+
if ( ! empty( $request['sticky'] ) ) {
|
285 |
+
stick_post( $post_id );
|
286 |
+
} else {
|
287 |
+
unstick_post( $post_id );
|
288 |
+
}
|
289 |
+
}
|
290 |
+
|
291 |
+
if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
|
292 |
+
$this->handle_template( $request['template'], $post->ID );
|
293 |
+
}
|
294 |
+
|
295 |
+
$this->update_additional_fields_for_object( get_post( $post_id ), $request );
|
296 |
+
|
297 |
+
/**
|
298 |
+
* @TODO: Enable rest_insert_post() action after
|
299 |
+
* Media Controller has been migrated to new style.
|
300 |
+
*
|
301 |
+
* do_action( 'rest_insert_post', $post, $request );
|
302 |
+
*/
|
303 |
+
|
304 |
+
return $this->get_item( array(
|
305 |
+
'id' => $post_id,
|
306 |
+
'context' => 'edit',
|
307 |
+
));
|
308 |
+
}
|
309 |
+
|
310 |
+
/**
|
311 |
+
* Delete a single post
|
312 |
+
*
|
313 |
+
* @param WP_REST_Request $request Full details about the request
|
314 |
+
* @return array|WP_Error
|
315 |
+
*/
|
316 |
+
public function delete_item( $request ) {
|
317 |
+
$id = (int) $request['id'];
|
318 |
+
$force = (bool) $request['force'];
|
319 |
+
|
320 |
+
$post = get_post( $id );
|
321 |
+
|
322 |
+
if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
|
323 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
|
324 |
+
}
|
325 |
+
|
326 |
+
$supports_trash = ( EMPTY_TRASH_DAYS > 0 );
|
327 |
+
if ( $post->post_type === 'attachment' ) {
|
328 |
+
$supports_trash = $supports_trash && MEDIA_TRASH;
|
329 |
+
}
|
330 |
+
|
331 |
+
/**
|
332 |
+
* Filter whether the post type supports trashing.
|
333 |
+
*
|
334 |
+
* @param boolean $supports_trash Does the post type support trashing?
|
335 |
+
* @param WP_Post $post Post we're attempting to trash.
|
336 |
+
*/
|
337 |
+
$supports_trash = apply_filters( 'rest_post_type_trashable', $supports_trash, $post );
|
338 |
+
|
339 |
+
if ( ! $this->check_delete_permission( $post ) ) {
|
340 |
+
return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => 401 ) );
|
341 |
+
}
|
342 |
+
|
343 |
+
$request = new WP_REST_Request( 'GET', '/wp/v2/' . $this->get_post_type_base( $this->post_type ) . '/' . $post->ID );
|
344 |
+
$request->set_param( 'context', 'edit' );
|
345 |
+
$response = rest_do_request( $request );
|
346 |
+
|
347 |
+
// If we're forcing, then delete permanently
|
348 |
+
if ( $force ) {
|
349 |
+
$result = wp_delete_post( $id, true );
|
350 |
+
} else {
|
351 |
+
// If we don't support trashing for this type, error out
|
352 |
+
if ( ! $supports_trash ) {
|
353 |
+
return new WP_Error( 'rest_trash_not_supported', __( 'The post does not support trashing.' ), array( 'status' => 501 ) );
|
354 |
+
}
|
355 |
+
|
356 |
+
// Otherwise, only trash if we haven't already
|
357 |
+
if ( 'trash' === $post->post_status ) {
|
358 |
+
return new WP_Error( 'rest_already_deleted', __( 'The post has already been deleted.' ), array( 'status' => 410 ) );
|
359 |
+
}
|
360 |
+
|
361 |
+
// (Note that internally this falls through to `wp_delete_post` if
|
362 |
+
// the trash is disabled.)
|
363 |
+
$result = wp_trash_post( $id );
|
364 |
+
}
|
365 |
+
|
366 |
+
if ( ! $result ) {
|
367 |
+
return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
|
368 |
+
}
|
369 |
+
|
370 |
+
return $response;
|
371 |
+
}
|
372 |
+
|
373 |
+
/**
|
374 |
+
* Check if a given request has access to read a post
|
375 |
+
*
|
376 |
+
* @param WP_REST_Request $request Full details about the request.
|
377 |
+
* @return bool|WP_Error
|
378 |
+
*/
|
379 |
+
public function get_item_permissions_check( $request ) {
|
380 |
+
|
381 |
+
$post = get_post( (int) $request['id'] );
|
382 |
+
|
383 |
+
if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
|
384 |
+
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => 403 ) );
|
385 |
+
}
|
386 |
+
|
387 |
+
if ( $post ) {
|
388 |
+
return $this->check_read_permission( $post );
|
389 |
+
}
|
390 |
+
|
391 |
+
return true;
|
392 |
+
}
|
393 |
+
|
394 |
+
/**
|
395 |
+
* Check if a given request has access to create a post
|
396 |
+
*
|
397 |
+
* @param WP_REST_Request $request Full details about the request.
|
398 |
+
* @return bool|WP_Error
|
399 |
+
*/
|
400 |
+
public function create_item_permissions_check( $request ) {
|
401 |
+
|
402 |
+
$post_type = get_post_type_object( $this->post_type );
|
403 |
+
|
404 |
+
if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
|
405 |
+
return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => 403 ) );
|
406 |
+
}
|
407 |
+
|
408 |
+
if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
|
409 |
+
return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to create posts as this user.' ), array( 'status' => 403 ) );
|
410 |
+
}
|
411 |
+
|
412 |
+
if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
|
413 |
+
return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => 403 ) );
|
414 |
+
}
|
415 |
+
|
416 |
+
return current_user_can( $post_type->cap->create_posts );
|
417 |
+
}
|
418 |
+
|
419 |
+
/**
|
420 |
+
* Check if a given request has access to update a post
|
421 |
+
*
|
422 |
+
* @param WP_REST_Request $request Full details about the request.
|
423 |
+
* @return bool|WP_Error
|
424 |
+
*/
|
425 |
+
public function update_item_permissions_check( $request ) {
|
426 |
+
|
427 |
+
$post = get_post( $request['id'] );
|
428 |
+
$post_type = get_post_type_object( $this->post_type );
|
429 |
+
|
430 |
+
if ( $post && ! $this->check_update_permission( $post ) ) {
|
431 |
+
return false;
|
432 |
+
}
|
433 |
+
|
434 |
+
if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
|
435 |
+
return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => 403 ) );
|
436 |
+
}
|
437 |
+
|
438 |
+
if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
|
439 |
+
return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to update posts as this user.' ), array( 'status' => 403 ) );
|
440 |
+
}
|
441 |
+
|
442 |
+
if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
|
443 |
+
return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => 403 ) );
|
444 |
+
}
|
445 |
+
|
446 |
+
return true;
|
447 |
+
}
|
448 |
+
|
449 |
+
/**
|
450 |
+
* Check if a given request has access to delete a post
|
451 |
+
*
|
452 |
+
* @param WP_REST_Request $request Full details about the request.
|
453 |
+
* @return bool|WP_Error
|
454 |
+
*/
|
455 |
+
public function delete_item_permissions_check( $request ) {
|
456 |
+
|
457 |
+
$post = get_post( $request['id'] );
|
458 |
+
|
459 |
+
if ( $post && ! $this->check_delete_permission( $post ) ) {
|
460 |
+
return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete posts.' ), array( 'status' => 403 ) );
|
461 |
+
}
|
462 |
+
|
463 |
+
return true;
|
464 |
+
}
|
465 |
+
|
466 |
+
/**
|
467 |
+
* Determine the allowed query_vars for a get_items() response and
|
468 |
+
* prepare for WP_Query.
|
469 |
+
*
|
470 |
+
* @param array $prepared_args
|
471 |
+
* @return array $query_args
|
472 |
+
*/
|
473 |
+
protected function prepare_items_query( $prepared_args = array() ) {
|
474 |
+
|
475 |
+
$valid_vars = array_flip( $this->get_allowed_query_vars() );
|
476 |
+
$query_args = array();
|
477 |
+
foreach ( $valid_vars as $var => $index ) {
|
478 |
+
if ( isset( $prepared_args[ $var ] ) ) {
|
479 |
+
$query_args[ $var ] = apply_filters( 'rest_query_var-' . $var, $prepared_args[ $var ] );
|
480 |
+
}
|
481 |
+
}
|
482 |
+
|
483 |
+
if ( empty( $query_args['post_status'] ) && 'attachment' === $this->post_type ) {
|
484 |
+
$query_args['post_status'] = 'inherit';
|
485 |
+
}
|
486 |
+
|
487 |
+
return $query_args;
|
488 |
+
}
|
489 |
+
|
490 |
+
/**
|
491 |
+
* Get all the WP Query vars that are allowed for the API request.
|
492 |
+
*
|
493 |
+
* @return array
|
494 |
+
*/
|
495 |
+
protected function get_allowed_query_vars() {
|
496 |
+
global $wp;
|
497 |
+
$valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
|
498 |
+
|
499 |
+
if ( current_user_can( 'edit_posts' ) ) {
|
500 |
+
/**
|
501 |
+
* Alter allowed query vars for authorized users.
|
502 |
+
*
|
503 |
+
* If the user has the `edit_posts` capability, we also allow use of
|
504 |
+
* private query parameters, which are only undesirable on the
|
505 |
+
* frontend, but are safe for use in query strings.
|
506 |
+
*
|
507 |
+
* To disable anyway, use
|
508 |
+
* `add_filter('rest_private_query_vars', '__return_empty_array');`
|
509 |
+
*
|
510 |
+
* @param array $private List of allowed query vars for authorized users.
|
511 |
+
*/
|
512 |
+
$private = apply_filters( 'rest_private_query_vars', $wp->private_query_vars );
|
513 |
+
$valid_vars = array_merge( $valid_vars, $private );
|
514 |
+
}
|
515 |
+
// Define our own in addition to WP's normal vars
|
516 |
+
$rest_valid = array( 'posts_per_page', 'ignore_sticky_posts', 'post_parent' );
|
517 |
+
$valid_vars = array_merge( $valid_vars, $rest_valid );
|
518 |
+
|
519 |
+
/**
|
520 |
+
* Alter allowed query vars for the REST API.
|
521 |
+
*
|
522 |
+
* This filter allows you to add or remove query vars from the allowed
|
523 |
+
* list for all requests, including unauthenticated ones. To alter the
|
524 |
+
* vars for editors only, {@see rest_private_query_vars}.
|
525 |
+
*
|
526 |
+
* @param array $valid_vars List of allowed query vars.
|
527 |
+
*/
|
528 |
+
$valid_vars = apply_filters( 'rest_query_vars', $valid_vars );
|
529 |
+
|
530 |
+
return $valid_vars;
|
531 |
+
}
|
532 |
+
|
533 |
+
/**
|
534 |
+
* Check the post excerpt and prepare it for single post output
|
535 |
+
*
|
536 |
+
* @param string $excerpt
|
537 |
+
* @return string|null $excerpt
|
538 |
+
*/
|
539 |
+
protected function prepare_excerpt_response( $excerpt ) {
|
540 |
+
if ( post_password_required() ) {
|
541 |
+
return __( 'There is no excerpt because this is a protected post.' );
|
542 |
+
}
|
543 |
+
|
544 |
+
$excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $excerpt ) );
|
545 |
+
|
546 |
+
if ( empty( $excerpt ) ) {
|
547 |
+
return '';
|
548 |
+
}
|
549 |
+
|
550 |
+
return $excerpt;
|
551 |
+
}
|
552 |
+
|
553 |
+
/**
|
554 |
+
* Check the post_date_gmt or modified_gmt and prepare any post or
|
555 |
+
* modified date for single post output.
|
556 |
+
*
|
557 |
+
* @param string $date_gmt
|
558 |
+
* @param string|null $date
|
559 |
+
* @return string|null ISO8601/RFC3339 formatted datetime.
|
560 |
+
*/
|
561 |
+
protected function prepare_date_response( $date_gmt, $date = null ) {
|
562 |
+
if ( '0000-00-00 00:00:00' === $date_gmt ) {
|
563 |
+
return null;
|
564 |
+
}
|
565 |
+
|
566 |
+
if ( isset( $date ) ) {
|
567 |
+
return rest_mysql_to_rfc3339( $date );
|
568 |
+
}
|
569 |
+
|
570 |
+
return rest_mysql_to_rfc3339( $date_gmt );
|
571 |
+
}
|
572 |
+
|
573 |
+
protected function prepare_password_response( $password ) {
|
574 |
+
if ( ! empty( $password ) ) {
|
575 |
+
/**
|
576 |
+
* Fake the correct cookie to fool post_password_required().
|
577 |
+
* Without this, get_the_content() will give a password form.
|
578 |
+
*/
|
579 |
+
require_once ABSPATH . 'wp-includes/class-phpass.php';
|
580 |
+
$hasher = new PasswordHash( 8, true );
|
581 |
+
$value = $hasher->HashPassword( $password );
|
582 |
+
$_COOKIE[ 'wp-postpass_' . COOKIEHASH ] = wp_slash( $value );
|
583 |
+
}
|
584 |
+
|
585 |
+
return $password;
|
586 |
+
}
|
587 |
+
|
588 |
+
/**
|
589 |
+
* Prepare a single post for create or update
|
590 |
+
*
|
591 |
+
* @param WP_REST_Request $request Request object
|
592 |
+
* @return WP_Error|obj $prepared_post Post object
|
593 |
+
*/
|
594 |
+
protected function prepare_item_for_database( $request ) {
|
595 |
+
$prepared_post = new stdClass;
|
596 |
+
|
597 |
+
// ID
|
598 |
+
if ( isset( $request['id'] ) ) {
|
599 |
+
$prepared_post->ID = absint( $request['id'] );
|
600 |
+
}
|
601 |
+
|
602 |
+
$schema = $this->get_item_schema();
|
603 |
+
|
604 |
+
// Post title
|
605 |
+
if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
|
606 |
+
if ( is_string( $request['title'] ) ) {
|
607 |
+
$prepared_post->post_title = wp_filter_post_kses( $request['title'] );
|
608 |
+
} elseif ( ! empty( $request['title']['raw'] ) ) {
|
609 |
+
$prepared_post->post_title = wp_filter_post_kses( $request['title']['raw'] );
|
610 |
+
}
|
611 |
+
}
|
612 |
+
|
613 |
+
// Post content
|
614 |
+
if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
|
615 |
+
if ( is_string( $request['content'] ) ) {
|
616 |
+
$prepared_post->post_content = wp_filter_post_kses( $request['content'] );
|
617 |
+
} elseif ( isset( $request['content']['raw'] ) ) {
|
618 |
+
$prepared_post->post_content = wp_filter_post_kses( $request['content']['raw'] );
|
619 |
+
}
|
620 |
+
}
|
621 |
+
|
622 |
+
// Post excerpt
|
623 |
+
if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
|
624 |
+
if ( is_string( $request['excerpt'] ) ) {
|
625 |
+
$prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt'] );
|
626 |
+
} elseif ( isset( $request['excerpt']['raw'] ) ) {
|
627 |
+
$prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt']['raw'] );
|
628 |
+
}
|
629 |
+
}
|
630 |
+
|
631 |
+
// Post type
|
632 |
+
if ( empty( $request['id'] ) ) {
|
633 |
+
// Creating new post, use default type for the controller
|
634 |
+
$prepared_post->post_type = $this->post_type;
|
635 |
+
} else {
|
636 |
+
// Updating a post, use previous type.
|
637 |
+
$prepared_post->post_type = get_post_type( $request['id'] );
|
638 |
+
}
|
639 |
+
$post_type = get_post_type_object( $prepared_post->post_type );
|
640 |
+
|
641 |
+
// Post status
|
642 |
+
if ( isset( $request['status'] ) ) {
|
643 |
+
$status = $this->handle_status_param( $request['status'], $post_type );
|
644 |
+
if ( is_wp_error( $status ) ) {
|
645 |
+
return $status;
|
646 |
+
}
|
647 |
+
|
648 |
+
$prepared_post->post_status = $status;
|
649 |
+
}
|
650 |
+
|
651 |
+
// Post date
|
652 |
+
if ( ! empty( $request['date'] ) ) {
|
653 |
+
$date_data = rest_get_date_with_gmt( $request['date'] );
|
654 |
+
|
655 |
+
if ( ! empty( $date_data ) ) {
|
656 |
+
list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data;
|
657 |
+
} else {
|
658 |
+
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ), array( 'status' => 400 ) );
|
659 |
+
}
|
660 |
+
} elseif ( ! empty( $request['date_gmt'] ) ) {
|
661 |
+
$date_data = rest_get_date_with_gmt( $request['date_gmt'], true );
|
662 |
+
|
663 |
+
if ( ! empty( $date_data ) ) {
|
664 |
+
list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data;
|
665 |
+
} else {
|
666 |
+
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ), array( 'status' => 400 ) );
|
667 |
+
}
|
668 |
+
}
|
669 |
+
// Post slug
|
670 |
+
if ( isset( $request['slug'] ) ) {
|
671 |
+
$prepared_post->post_name = sanitize_title( $request['slug'] );
|
672 |
+
}
|
673 |
+
|
674 |
+
// Author
|
675 |
+
if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) {
|
676 |
+
$author = $this->handle_author_param( $request['author'], $post_type );
|
677 |
+
if ( is_wp_error( $author ) ) {
|
678 |
+
return $author;
|
679 |
+
}
|
680 |
+
|
681 |
+
$prepared_post->post_author = $author;
|
682 |
+
}
|
683 |
+
|
684 |
+
// Post password
|
685 |
+
if ( isset( $request['password'] ) ) {
|
686 |
+
$prepared_post->post_password = $request['password'];
|
687 |
+
|
688 |
+
if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) {
|
689 |
+
return new WP_Error( 'rest_invalid_field', __( 'A post can not be sticky and have a password.' ), array( 'status' => 400 ) );
|
690 |
+
}
|
691 |
+
|
692 |
+
if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) {
|
693 |
+
return new WP_Error( 'rest_invalid_field', __( 'A sticky post can not be password protected.' ), array( 'status' => 400 ) );
|
694 |
+
}
|
695 |
+
}
|
696 |
+
|
697 |
+
if ( ! empty( $request['sticky'] ) ) {
|
698 |
+
if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) {
|
699 |
+
return new WP_Error( 'rest_invalid_field', __( 'A password protected post can not be set to sticky.' ), array( 'status' => 400 ) );
|
700 |
+
}
|
701 |
+
}
|
702 |
+
|
703 |
+
// Parent
|
704 |
+
$post_type_obj = get_post_type_object( $this->post_type );
|
705 |
+
if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) {
|
706 |
+
$parent = get_post( (int) $request['parent'] );
|
707 |
+
if ( empty( $parent ) ) {
|
708 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent ID.' ), array( 'status' => 400 ) );
|
709 |
+
}
|
710 |
+
|
711 |
+
$prepared_post->post_parent = (int) $parent->ID;
|
712 |
+
}
|
713 |
+
|
714 |
+
// Menu order
|
715 |
+
if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) {
|
716 |
+
$prepared_post->menu_order = (int) $request['menu_order'];
|
717 |
+
}
|
718 |
+
|
719 |
+
// Comment status
|
720 |
+
if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
|
721 |
+
$prepared_post->comment_status = sanitize_text_field( $request['comment_status'] );
|
722 |
+
}
|
723 |
+
|
724 |
+
// Ping status
|
725 |
+
if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
|
726 |
+
$prepared_post->ping_status = sanitize_text_field( $request['ping_status'] );
|
727 |
+
}
|
728 |
+
|
729 |
+
return apply_filters( 'rest_pre_insert_' . $this->post_type, $prepared_post, $request );
|
730 |
+
}
|
731 |
+
|
732 |
+
/**
|
733 |
+
* Determine validity and normalize provided status param.
|
734 |
+
*
|
735 |
+
* @param string $post_status
|
736 |
+
* @param object $post_type
|
737 |
+
* @return WP_Error|string $post_status
|
738 |
+
*/
|
739 |
+
protected function handle_status_param( $post_status, $post_type ) {
|
740 |
+
$post_status = sanitize_text_field( $post_status );
|
741 |
+
|
742 |
+
switch ( $post_status ) {
|
743 |
+
case 'draft':
|
744 |
+
case 'pending':
|
745 |
+
break;
|
746 |
+
case 'private':
|
747 |
+
if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
|
748 |
+
return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create private posts in this post type' ), array( 'status' => 403 ) );
|
749 |
+
}
|
750 |
+
break;
|
751 |
+
case 'publish':
|
752 |
+
case 'future':
|
753 |
+
if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
|
754 |
+
return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to publish posts in this post type' ), array( 'status' => 403 ) );
|
755 |
+
}
|
756 |
+
break;
|
757 |
+
default:
|
758 |
+
if ( ! get_post_status_object( $post_status ) ) {
|
759 |
+
$post_status = 'draft';
|
760 |
+
}
|
761 |
+
break;
|
762 |
+
}
|
763 |
+
|
764 |
+
return $post_status;
|
765 |
+
}
|
766 |
+
|
767 |
+
/**
|
768 |
+
* Determine validity and normalize provided author param.
|
769 |
+
*
|
770 |
+
* @param object|integer $post_author
|
771 |
+
* @param object $post_type
|
772 |
+
* @return WP_Error|integer $post_author
|
773 |
+
*/
|
774 |
+
protected function handle_author_param( $post_author, $post_type ) {
|
775 |
+
if ( is_object( $post_author ) ) {
|
776 |
+
if ( empty( $post_author->id ) ) {
|
777 |
+
return new WP_Error( 'rest_invalid_author', __( 'Invalid author object.' ), array( 'status' => 400 ) );
|
778 |
+
}
|
779 |
+
$post_author = (int) $post_author->id;
|
780 |
+
} else {
|
781 |
+
$post_author = (int) $post_author;
|
782 |
+
}
|
783 |
+
|
784 |
+
// Only check edit others' posts if we are another user
|
785 |
+
if ( get_current_user_id() !== $post_author ) {
|
786 |
+
|
787 |
+
$author = get_userdata( $post_author );
|
788 |
+
|
789 |
+
if ( ! $author ) {
|
790 |
+
return new WP_Error( 'rest_invalid_author', __( 'Invalid author ID.' ), array( 'status' => 400 ) );
|
791 |
+
}
|
792 |
+
}
|
793 |
+
|
794 |
+
return $post_author;
|
795 |
+
}
|
796 |
+
|
797 |
+
/**
|
798 |
+
* Determine the featured image based on a request param
|
799 |
+
*
|
800 |
+
* @param int $featured_image
|
801 |
+
* @param int $post_id
|
802 |
+
*/
|
803 |
+
protected function handle_featured_image( $featured_image, $post_id ) {
|
804 |
+
|
805 |
+
$featured_image = (int) $featured_image;
|
806 |
+
if ( $featured_image ) {
|
807 |
+
$result = set_post_thumbnail( $post_id, $featured_image );
|
808 |
+
if ( $result ) {
|
809 |
+
return true;
|
810 |
+
} else {
|
811 |
+
return new WP_Error( 'rest_invalid_featured_image', __( 'Invalid featured image ID.' ), array( 'status' => 400 ) );
|
812 |
+
}
|
813 |
+
} else {
|
814 |
+
return delete_post_thumbnail( $post_id );
|
815 |
+
}
|
816 |
+
|
817 |
+
}
|
818 |
+
|
819 |
+
/**
|
820 |
+
* Set the template for a page
|
821 |
+
*
|
822 |
+
* @param string $template
|
823 |
+
* @param integer $post_id
|
824 |
+
*/
|
825 |
+
public function handle_template( $template, $post_id ) {
|
826 |
+
if ( in_array( $template, array_values( get_page_templates() ) ) ) {
|
827 |
+
update_post_meta( $post_id, '_wp_page_template', $template );
|
828 |
+
} else {
|
829 |
+
update_post_meta( $post_id, '_wp_page_template', '' );
|
830 |
+
}
|
831 |
+
}
|
832 |
+
|
833 |
+
/**
|
834 |
+
* Check if a given post type should be viewed or managed.
|
835 |
+
*
|
836 |
+
* @param object|string $post_type
|
837 |
+
* @return bool Is post type allowed?
|
838 |
+
*/
|
839 |
+
protected function check_is_post_type_allowed( $post_type ) {
|
840 |
+
if ( ! is_object( $post_type ) ) {
|
841 |
+
$post_type = get_post_type_object( $post_type );
|
842 |
+
}
|
843 |
+
|
844 |
+
if ( ! empty( $post_type ) && $post_type->show_in_rest ) {
|
845 |
+
return true;
|
846 |
+
}
|
847 |
+
|
848 |
+
return false;
|
849 |
+
}
|
850 |
+
|
851 |
+
/**
|
852 |
+
* Check if we can read a post
|
853 |
+
*
|
854 |
+
* Correctly handles posts with the inherit status.
|
855 |
+
*
|
856 |
+
* @param obj $post Post object
|
857 |
+
* @return bool Can we read it?
|
858 |
+
*/
|
859 |
+
public function check_read_permission( $post ) {
|
860 |
+
if ( ! empty( $post->post_password ) && ! $this->check_update_permission( $post ) ) {
|
861 |
+
return false;
|
862 |
+
}
|
863 |
+
|
864 |
+
$post_type = get_post_type_object( $post->post_type );
|
865 |
+
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
|
866 |
+
return false;
|
867 |
+
}
|
868 |
+
|
869 |
+
// Can we read the post?
|
870 |
+
if ( 'publish' === $post->post_status || current_user_can( $post_type->cap->read_post, $post->ID ) ) {
|
871 |
+
return true;
|
872 |
+
}
|
873 |
+
|
874 |
+
// Can we read the parent if we're inheriting?
|
875 |
+
if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
|
876 |
+
$parent = get_post( $post->post_parent );
|
877 |
+
|
878 |
+
if ( $this->check_read_permission( $parent ) ) {
|
879 |
+
return true;
|
880 |
+
}
|
881 |
+
}
|
882 |
+
|
883 |
+
// If we don't have a parent, but the status is set to inherit, assume
|
884 |
+
// it's published (as per get_post_status())
|
885 |
+
if ( 'inherit' === $post->post_status ) {
|
886 |
+
return true;
|
887 |
+
}
|
888 |
+
|
889 |
+
return false;
|
890 |
+
}
|
891 |
+
|
892 |
+
/**
|
893 |
+
* Check if we can edit a post
|
894 |
+
*
|
895 |
+
* @param obj $post Post object
|
896 |
+
* @return bool Can we edit it?
|
897 |
+
*/
|
898 |
+
protected function check_update_permission( $post ) {
|
899 |
+
$post_type = get_post_type_object( $post->post_type );
|
900 |
+
|
901 |
+
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
|
902 |
+
return false;
|
903 |
+
}
|
904 |
+
|
905 |
+
return current_user_can( $post_type->cap->edit_post, $post->ID );
|
906 |
+
}
|
907 |
+
|
908 |
+
/**
|
909 |
+
* Check if we can create a post
|
910 |
+
*
|
911 |
+
* @param obj $post Post object
|
912 |
+
* @return bool Can we create it?
|
913 |
+
*/
|
914 |
+
protected function check_create_permission( $post ) {
|
915 |
+
$post_type = get_post_type_object( $post->post_type );
|
916 |
+
|
917 |
+
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
|
918 |
+
return false;
|
919 |
+
}
|
920 |
+
|
921 |
+
return current_user_can( $post_type->cap->create_posts );
|
922 |
+
}
|
923 |
+
|
924 |
+
/**
|
925 |
+
* Check if we can delete a post
|
926 |
+
*
|
927 |
+
* @param obj $post Post object
|
928 |
+
* @return bool Can we delete it?
|
929 |
+
*/
|
930 |
+
protected function check_delete_permission( $post ) {
|
931 |
+
$post_type = get_post_type_object( $post->post_type );
|
932 |
+
|
933 |
+
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
|
934 |
+
return false;
|
935 |
+
}
|
936 |
+
|
937 |
+
return current_user_can( $post_type->cap->delete_post, $post->ID );
|
938 |
+
}
|
939 |
+
|
940 |
+
/**
|
941 |
+
* Get the base path for a post type's endpoints.
|
942 |
+
*
|
943 |
+
* @param object|string $post_type
|
944 |
+
* @return string $base
|
945 |
+
*/
|
946 |
+
public function get_post_type_base( $post_type ) {
|
947 |
+
if ( ! is_object( $post_type ) ) {
|
948 |
+
$post_type = get_post_type_object( $post_type );
|
949 |
+
}
|
950 |
+
|
951 |
+
$base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
|
952 |
+
|
953 |
+
return $base;
|
954 |
+
}
|
955 |
+
|
956 |
+
/**
|
957 |
+
* Prepare a single post output for response
|
958 |
+
*
|
959 |
+
* @param WP_Post $post Post object
|
960 |
+
* @param WP_REST_Request $request Request object
|
961 |
+
* @return WP_REST_Response $data
|
962 |
+
*/
|
963 |
+
public function prepare_item_for_response( $post, $request ) {
|
964 |
+
$GLOBALS['post'] = $post;
|
965 |
+
setup_postdata( $post );
|
966 |
+
|
967 |
+
// Base fields for every post
|
968 |
+
$data = array(
|
969 |
+
'id' => $post->ID,
|
970 |
+
'date' => $this->prepare_date_response( $post->post_date_gmt, $post->post_date ),
|
971 |
+
'date_gmt' => $this->prepare_date_response( $post->post_date_gmt ),
|
972 |
+
'guid' => array(
|
973 |
+
'rendered' => apply_filters( 'get_the_guid', $post->guid ),
|
974 |
+
'raw' => $post->guid,
|
975 |
+
),
|
976 |
+
'modified' => $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ),
|
977 |
+
'modified_gmt' => $this->prepare_date_response( $post->post_modified_gmt ),
|
978 |
+
'password' => $post->post_password,
|
979 |
+
'slug' => $post->post_name,
|
980 |
+
'status' => $post->post_status,
|
981 |
+
'type' => $post->post_type,
|
982 |
+
'link' => get_permalink( $post->ID ),
|
983 |
+
);
|
984 |
+
|
985 |
+
$schema = $this->get_item_schema();
|
986 |
+
|
987 |
+
if ( ! empty( $schema['properties']['title'] ) ) {
|
988 |
+
$data['title'] = array(
|
989 |
+
'raw' => $post->post_title,
|
990 |
+
'rendered' => get_the_title( $post->ID ),
|
991 |
+
);
|
992 |
+
}
|
993 |
+
|
994 |
+
if ( ! empty( $schema['properties']['content'] ) ) {
|
995 |
+
|
996 |
+
if ( ! empty( $post->post_password ) ) {
|
997 |
+
$this->prepare_password_response( $post->post_password );
|
998 |
+
}
|
999 |
+
|
1000 |
+
$data['content'] = array(
|
1001 |
+
'raw' => $post->post_content,
|
1002 |
+
'rendered' => apply_filters( 'the_content', $post->post_content ),
|
1003 |
+
);
|
1004 |
+
|
1005 |
+
// Don't leave our cookie lying around: https://github.com/WP-API/WP-API/issues/1055
|
1006 |
+
if ( ! empty( $post->post_password ) ) {
|
1007 |
+
$_COOKIE[ 'wp-postpass_' . COOKIEHASH ] = '';
|
1008 |
+
}
|
1009 |
+
}
|
1010 |
+
|
1011 |
+
if ( ! empty( $schema['properties']['excerpt'] ) ) {
|
1012 |
+
$data['excerpt'] = array(
|
1013 |
+
'raw' => $post->post_excerpt,
|
1014 |
+
'rendered' => $this->prepare_excerpt_response( $post->post_excerpt ),
|
1015 |
+
);
|
1016 |
+
}
|
1017 |
+
|
1018 |
+
if ( ! empty( $schema['properties']['author'] ) ) {
|
1019 |
+
$data['author'] = (int) $post->post_author;
|
1020 |
+
}
|
1021 |
+
|
1022 |
+
if ( ! empty( $schema['properties']['featured_image'] ) ) {
|
1023 |
+
$data['featured_image'] = (int) get_post_thumbnail_id( $post->ID );
|
1024 |
+
}
|
1025 |
+
|
1026 |
+
if ( ! empty( $schema['properties']['parent'] ) ) {
|
1027 |
+
$data['parent'] = (int) $post->post_parent;
|
1028 |
+
}
|
1029 |
+
|
1030 |
+
if ( ! empty( $schema['properties']['menu_order'] ) ) {
|
1031 |
+
$data['menu_order'] = (int) $post->menu_order;
|
1032 |
+
}
|
1033 |
+
|
1034 |
+
if ( ! empty( $schema['properties']['comment_status'] ) ) {
|
1035 |
+
$data['comment_status'] = $post->comment_status;
|
1036 |
+
}
|
1037 |
+
|
1038 |
+
if ( ! empty( $schema['properties']['ping_status'] ) ) {
|
1039 |
+
$data['ping_status'] = $post->ping_status;
|
1040 |
+
}
|
1041 |
+
|
1042 |
+
if ( ! empty( $schema['properties']['sticky'] ) ) {
|
1043 |
+
$data['sticky'] = is_sticky( $post->ID );
|
1044 |
+
}
|
1045 |
+
|
1046 |
+
if ( ! empty( $schema['properties']['template'] ) ) {
|
1047 |
+
if ( $template = get_page_template_slug( $post->ID ) ) {
|
1048 |
+
$data['template'] = $template;
|
1049 |
+
} else {
|
1050 |
+
$data['template'] = '';
|
1051 |
+
}
|
1052 |
+
}
|
1053 |
+
|
1054 |
+
if ( ! empty( $schema['properties']['format'] ) ) {
|
1055 |
+
$data['format'] = get_post_format( $post->ID );
|
1056 |
+
// Fill in blank post format
|
1057 |
+
if ( empty( $data['format'] ) ) {
|
1058 |
+
$data['format'] = 'standard';
|
1059 |
+
}
|
1060 |
+
}
|
1061 |
+
|
1062 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
1063 |
+
$data = $this->filter_response_by_context( $data, $context );
|
1064 |
+
|
1065 |
+
$data = $this->add_additional_fields_to_object( $data, $request );
|
1066 |
+
|
1067 |
+
// Wrap the data in a response object
|
1068 |
+
$data = rest_ensure_response( $data );
|
1069 |
+
|
1070 |
+
$data->add_links( $this->prepare_links( $post ) );
|
1071 |
+
|
1072 |
+
return apply_filters( 'rest_prepare_' . $this->post_type, $data, $post, $request );
|
1073 |
+
}
|
1074 |
+
|
1075 |
+
/**
|
1076 |
+
* Prepare links for the request.
|
1077 |
+
*
|
1078 |
+
* @param WP_Post $post Post object.
|
1079 |
+
* @return array Links for the given post.
|
1080 |
+
*/
|
1081 |
+
protected function prepare_links( $post ) {
|
1082 |
+
$base = '/wp/v2/' . $this->get_post_type_base( $this->post_type );
|
1083 |
+
|
1084 |
+
// Entity meta
|
1085 |
+
$links = array(
|
1086 |
+
'self' => array(
|
1087 |
+
'href' => rest_url( trailingslashit( $base ) . $post->ID ),
|
1088 |
+
),
|
1089 |
+
'collection' => array(
|
1090 |
+
'href' => rest_url( $base ),
|
1091 |
+
),
|
1092 |
+
);
|
1093 |
+
|
1094 |
+
if ( ( in_array( $post->post_type, array( 'post', 'page' ) ) || post_type_supports( $post->post_type, 'author' ) )
|
1095 |
+
&& ! empty( $post->post_author ) ) {
|
1096 |
+
$links['author'] = array(
|
1097 |
+
'href' => rest_url( '/wp/v2/users/' . $post->post_author ),
|
1098 |
+
'embeddable' => true,
|
1099 |
+
);
|
1100 |
+
};
|
1101 |
+
|
1102 |
+
if ( in_array( $post->post_type, array( 'post', 'page' ) ) || post_type_supports( $post->post_type, 'comments' ) ) {
|
1103 |
+
$replies_url = rest_url( '/wp/v2/comments' );
|
1104 |
+
$replies_url = add_query_arg( 'post_id', $post->ID, $replies_url );
|
1105 |
+
$links['replies'] = array(
|
1106 |
+
'href' => $replies_url,
|
1107 |
+
'embeddable' => true,
|
1108 |
+
);
|
1109 |
+
}
|
1110 |
+
|
1111 |
+
if ( in_array( $post->post_type, array( 'post', 'page' ) ) || post_type_supports( $post->post_type, 'revisions' ) ) {
|
1112 |
+
$links['version-history'] = array(
|
1113 |
+
'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions' ),
|
1114 |
+
);
|
1115 |
+
}
|
1116 |
+
$post_type_obj = get_post_type_object( $post->post_type );
|
1117 |
+
if ( $post_type_obj->hierarchical && ! empty( $post->post_parent ) ) {
|
1118 |
+
$links['up'] = array(
|
1119 |
+
'href' => rest_url( trailingslashit( $base ) . (int) $post->post_parent ),
|
1120 |
+
'embeddable' => true,
|
1121 |
+
);
|
1122 |
+
}
|
1123 |
+
|
1124 |
+
if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ) ) ) {
|
1125 |
+
$attachments_url = rest_url( 'wp/v2/media' );
|
1126 |
+
$attachments_url = add_query_arg( 'post_parent', $post->ID, $attachments_url );
|
1127 |
+
$links['http://v2.wp-api.org/attachment'] = array(
|
1128 |
+
'href' => $attachments_url,
|
1129 |
+
'embeddable' => true,
|
1130 |
+
);
|
1131 |
+
}
|
1132 |
+
|
1133 |
+
$taxonomies = get_object_taxonomies( $post->post_type );
|
1134 |
+
if ( ! empty( $taxonomies ) ) {
|
1135 |
+
$links['http://v2.wp-api.org/term'] = array();
|
1136 |
+
|
1137 |
+
foreach ( $taxonomies as $tax ) {
|
1138 |
+
$taxonomy_obj = get_taxonomy( $tax );
|
1139 |
+
// Skip taxonomies that are not public.
|
1140 |
+
if ( false === $taxonomy_obj->public ) {
|
1141 |
+
continue;
|
1142 |
+
}
|
1143 |
+
|
1144 |
+
if ( 'post_tag' === $tax ) {
|
1145 |
+
$terms_url = rest_url( '/wp/v2/terms/tag' );
|
1146 |
+
} else {
|
1147 |
+
$terms_url = rest_url( '/wp/v2/terms/' . $tax );
|
1148 |
+
}
|
1149 |
+
|
1150 |
+
$terms_url = add_query_arg( 'post', $post->ID, $terms_url );
|
1151 |
+
|
1152 |
+
$links['http://v2.wp-api.org/term'][] = array(
|
1153 |
+
'href' => $terms_url,
|
1154 |
+
'taxonomy' => $tax,
|
1155 |
+
'embeddable' => true,
|
1156 |
+
);
|
1157 |
+
}
|
1158 |
+
}
|
1159 |
+
|
1160 |
+
if ( post_type_supports( $post->post_type, 'custom-fields' ) ) {
|
1161 |
+
$links['http://v2.wp-api.org/meta'] = array(
|
1162 |
+
'href' => rest_url( trailingslashit( $base ) . $post->ID . '/meta' ),
|
1163 |
+
'embeddable' => true,
|
1164 |
+
);
|
1165 |
+
}
|
1166 |
+
|
1167 |
+
return $links;
|
1168 |
+
}
|
1169 |
+
|
1170 |
+
/**
|
1171 |
+
* Get the Post's schema, conforming to JSON Schema
|
1172 |
+
*
|
1173 |
+
* @return array
|
1174 |
+
*/
|
1175 |
+
public function get_item_schema() {
|
1176 |
+
|
1177 |
+
$base = $this->get_post_type_base( $this->post_type );
|
1178 |
+
$schema = array(
|
1179 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
1180 |
+
'title' => $this->post_type,
|
1181 |
+
'type' => 'object',
|
1182 |
+
/*
|
1183 |
+
* Base properties for every Post
|
1184 |
+
*/
|
1185 |
+
'properties' => array(
|
1186 |
+
'date' => array(
|
1187 |
+
'description' => 'The date the object was published.',
|
1188 |
+
'type' => 'string',
|
1189 |
+
'format' => 'date-time',
|
1190 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1191 |
+
),
|
1192 |
+
'date_gmt' => array(
|
1193 |
+
'description' => 'The date the object was published, as GMT.',
|
1194 |
+
'type' => 'string',
|
1195 |
+
'format' => 'date-time',
|
1196 |
+
'context' => array( 'edit' ),
|
1197 |
+
),
|
1198 |
+
'guid' => array(
|
1199 |
+
'description' => 'The globally unique identifier for the object.',
|
1200 |
+
'type' => 'object',
|
1201 |
+
'context' => array( 'view', 'edit' ),
|
1202 |
+
'readonly' => true,
|
1203 |
+
'properties' => array(
|
1204 |
+
'raw' => array(
|
1205 |
+
'description' => 'GUID for the object, as it exists in the database.',
|
1206 |
+
'type' => 'string',
|
1207 |
+
'context' => array( 'edit' ),
|
1208 |
+
),
|
1209 |
+
'rendered' => array(
|
1210 |
+
'description' => 'GUID for the object, transformed for display.',
|
1211 |
+
'type' => 'string',
|
1212 |
+
'context' => array( 'view', 'edit' ),
|
1213 |
+
),
|
1214 |
+
),
|
1215 |
+
),
|
1216 |
+
'id' => array(
|
1217 |
+
'description' => 'Unique identifier for the object.',
|
1218 |
+
'type' => 'integer',
|
1219 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1220 |
+
'readonly' => true,
|
1221 |
+
),
|
1222 |
+
'link' => array(
|
1223 |
+
'description' => 'URL to the object.',
|
1224 |
+
'type' => 'string',
|
1225 |
+
'format' => 'uri',
|
1226 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1227 |
+
'readonly' => true,
|
1228 |
+
),
|
1229 |
+
'modified' => array(
|
1230 |
+
'description' => 'The date the object was last modified.',
|
1231 |
+
'type' => 'string',
|
1232 |
+
'format' => 'date-time',
|
1233 |
+
'context' => array( 'view', 'edit' ),
|
1234 |
+
),
|
1235 |
+
'modified_gmt' => array(
|
1236 |
+
'description' => 'The date the object was last modified, as GMT.',
|
1237 |
+
'type' => 'string',
|
1238 |
+
'format' => 'date-time',
|
1239 |
+
'context' => array( 'view', 'edit' ),
|
1240 |
+
),
|
1241 |
+
'password' => array(
|
1242 |
+
'description' => 'A password to protect access to the post.',
|
1243 |
+
'type' => 'string',
|
1244 |
+
'context' => array( 'edit' ),
|
1245 |
+
),
|
1246 |
+
'slug' => array(
|
1247 |
+
'description' => 'An alphanumeric identifier for the object unique to its type.',
|
1248 |
+
'type' => 'string',
|
1249 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1250 |
+
),
|
1251 |
+
'status' => array(
|
1252 |
+
'description' => 'A named status for the object.',
|
1253 |
+
'type' => 'string',
|
1254 |
+
'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ),
|
1255 |
+
'context' => array( 'edit' ),
|
1256 |
+
),
|
1257 |
+
'type' => array(
|
1258 |
+
'description' => 'Type of Post for the object.',
|
1259 |
+
'type' => 'string',
|
1260 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1261 |
+
'readonly' => true,
|
1262 |
+
),
|
1263 |
+
),
|
1264 |
+
);
|
1265 |
+
|
1266 |
+
$post_type_obj = get_post_type_object( $this->post_type );
|
1267 |
+
if ( $post_type_obj->hierarchical ) {
|
1268 |
+
$schema['properties']['parent'] = array(
|
1269 |
+
'description' => 'The ID for the parent of the object.',
|
1270 |
+
'type' => 'integer',
|
1271 |
+
'context' => array( 'view', 'edit' ),
|
1272 |
+
);
|
1273 |
+
}
|
1274 |
+
|
1275 |
+
$post_type_attributes = array(
|
1276 |
+
'title',
|
1277 |
+
'editor',
|
1278 |
+
'author',
|
1279 |
+
'excerpt',
|
1280 |
+
'thumbnail',
|
1281 |
+
'comments',
|
1282 |
+
'revisions',
|
1283 |
+
'page-attributes',
|
1284 |
+
'post-formats',
|
1285 |
+
);
|
1286 |
+
$fixed_schemas = array(
|
1287 |
+
'post' => array(
|
1288 |
+
'title',
|
1289 |
+
'editor',
|
1290 |
+
'author',
|
1291 |
+
'excerpt',
|
1292 |
+
'thumbnail',
|
1293 |
+
'comments',
|
1294 |
+
'revisions',
|
1295 |
+
'post-formats',
|
1296 |
+
),
|
1297 |
+
'page' => array(
|
1298 |
+
'title',
|
1299 |
+
'editor',
|
1300 |
+
'author',
|
1301 |
+
'excerpt',
|
1302 |
+
'thumbnail',
|
1303 |
+
'comments',
|
1304 |
+
'revisions',
|
1305 |
+
'page-attributes',
|
1306 |
+
),
|
1307 |
+
'attachment' => array(
|
1308 |
+
'title',
|
1309 |
+
'author',
|
1310 |
+
'comments',
|
1311 |
+
'revisions',
|
1312 |
+
),
|
1313 |
+
);
|
1314 |
+
foreach ( $post_type_attributes as $attribute ) {
|
1315 |
+
if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ] ) ) {
|
1316 |
+
continue;
|
1317 |
+
} elseif ( ! in_array( $this->post_type, array_keys( $fixed_schemas ) ) && ! post_type_supports( $this->post_type, $attribute ) ) {
|
1318 |
+
continue;
|
1319 |
+
}
|
1320 |
+
|
1321 |
+
switch ( $attribute ) {
|
1322 |
+
|
1323 |
+
case 'title':
|
1324 |
+
$schema['properties']['title'] = array(
|
1325 |
+
'description' => 'The title for the object.',
|
1326 |
+
'type' => 'object',
|
1327 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1328 |
+
'properties' => array(
|
1329 |
+
'raw' => array(
|
1330 |
+
'description' => 'Title for the object, as it exists in the database.',
|
1331 |
+
'type' => 'string',
|
1332 |
+
'context' => array( 'edit' ),
|
1333 |
+
),
|
1334 |
+
'rendered' => array(
|
1335 |
+
'description' => 'Title for the object, transformed for display.',
|
1336 |
+
'type' => 'string',
|
1337 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1338 |
+
),
|
1339 |
+
),
|
1340 |
+
);
|
1341 |
+
break;
|
1342 |
+
|
1343 |
+
case 'editor':
|
1344 |
+
$schema['properties']['content'] = array(
|
1345 |
+
'description' => 'The content for the object.',
|
1346 |
+
'type' => 'object',
|
1347 |
+
'context' => array( 'view', 'edit' ),
|
1348 |
+
'properties' => array(
|
1349 |
+
'raw' => array(
|
1350 |
+
'description' => 'Content for the object, as it exists in the database.',
|
1351 |
+
'type' => 'string',
|
1352 |
+
'context' => array( 'edit' ),
|
1353 |
+
),
|
1354 |
+
'rendered' => array(
|
1355 |
+
'description' => 'Content for the object, transformed for display.',
|
1356 |
+
'type' => 'string',
|
1357 |
+
'context' => array( 'view', 'edit' ),
|
1358 |
+
),
|
1359 |
+
),
|
1360 |
+
);
|
1361 |
+
break;
|
1362 |
+
|
1363 |
+
case 'author':
|
1364 |
+
$schema['properties']['author'] = array(
|
1365 |
+
'description' => 'The ID for the author of the object.',
|
1366 |
+
'type' => 'integer',
|
1367 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1368 |
+
);
|
1369 |
+
break;
|
1370 |
+
|
1371 |
+
case 'excerpt':
|
1372 |
+
$schema['properties']['excerpt'] = array(
|
1373 |
+
'description' => 'The excerpt for the object.',
|
1374 |
+
'type' => 'object',
|
1375 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1376 |
+
'properties' => array(
|
1377 |
+
'raw' => array(
|
1378 |
+
'description' => 'Excerpt for the object, as it exists in the database.',
|
1379 |
+
'type' => 'string',
|
1380 |
+
'context' => array( 'edit' ),
|
1381 |
+
),
|
1382 |
+
'rendered' => array(
|
1383 |
+
'description' => 'Excerpt for the object, transformed for display.',
|
1384 |
+
'type' => 'string',
|
1385 |
+
'context' => array( 'view', 'edit', 'embed' ),
|
1386 |
+
),
|
1387 |
+
),
|
1388 |
+
);
|
1389 |
+
break;
|
1390 |
+
|
1391 |
+
case 'thumbnail':
|
1392 |
+
$schema['properties']['featured_image'] = array(
|
1393 |
+
'description' => 'ID of the featured image for the object.',
|
1394 |
+
'type' => 'integer',
|
1395 |
+
'context' => array( 'view', 'edit' ),
|
1396 |
+
);
|
1397 |
+
break;
|
1398 |
+
|
1399 |
+
case 'comments':
|
1400 |
+
$schema['properties']['comment_status'] = array(
|
1401 |
+
'description' => 'Whether or not comments are open on the object.',
|
1402 |
+
'type' => 'string',
|
1403 |
+
'enum' => array( 'open', 'closed' ),
|
1404 |
+
'context' => array( 'view', 'edit' ),
|
1405 |
+
);
|
1406 |
+
$schema['properties']['ping_status'] = array(
|
1407 |
+
'description' => 'Whether or not the object can be pinged.',
|
1408 |
+
'type' => 'string',
|
1409 |
+
'enum' => array( 'open', 'closed' ),
|
1410 |
+
'context' => array( 'view', 'edit' ),
|
1411 |
+
);
|
1412 |
+
break;
|
1413 |
+
|
1414 |
+
case 'page-attributes':
|
1415 |
+
$schema['properties']['menu_order'] = array(
|
1416 |
+
'description' => 'The order of the object in relation to other object of its type.',
|
1417 |
+
'type' => 'integer',
|
1418 |
+
'context' => array( 'view', 'edit' ),
|
1419 |
+
);
|
1420 |
+
break;
|
1421 |
+
|
1422 |
+
case 'post-formats':
|
1423 |
+
$schema['properties']['format'] = array(
|
1424 |
+
'description' => 'The format for the object.',
|
1425 |
+
'type' => 'string',
|
1426 |
+
'enum' => get_post_format_slugs(),
|
1427 |
+
'context' => array( 'view', 'edit' ),
|
1428 |
+
);
|
1429 |
+
break;
|
1430 |
+
|
1431 |
+
}
|
1432 |
+
}
|
1433 |
+
|
1434 |
+
if ( 'post' === $this->post_type ) {
|
1435 |
+
$schema['properties']['sticky'] = array(
|
1436 |
+
'description' => 'Whether or not the object should be treated as sticky.',
|
1437 |
+
'type' => 'boolean',
|
1438 |
+
'context' => array( 'view', 'edit' ),
|
1439 |
+
);
|
1440 |
+
}
|
1441 |
+
|
1442 |
+
if ( 'page' === $this->post_type ) {
|
1443 |
+
$schema['properties']['template'] = array(
|
1444 |
+
'description' => 'The theme file to use to display the object.',
|
1445 |
+
'type' => 'string',
|
1446 |
+
'enum' => array_values( get_page_templates() ),
|
1447 |
+
'context' => array( 'view', 'edit' ),
|
1448 |
+
);
|
1449 |
+
}
|
1450 |
+
|
1451 |
+
return $this->add_additional_fields_schema( $schema );
|
1452 |
+
}
|
1453 |
+
|
1454 |
+
}
|
lib/endpoints/class-wp-rest-posts-terms-controller.php
ADDED
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
|
4 |
+
|
5 |
+
protected $post_type;
|
6 |
+
|
7 |
+
public function __construct( $post_type, $taxonomy ) {
|
8 |
+
$this->post_type = $post_type;
|
9 |
+
$this->taxonomy = $taxonomy;
|
10 |
+
$this->posts_controller = new WP_REST_Posts_Controller( $post_type );
|
11 |
+
$this->terms_controller = new WP_REST_Terms_Controller( $taxonomy );
|
12 |
+
}
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Register the routes for the objects of the controller.
|
16 |
+
*/
|
17 |
+
public function register_routes() {
|
18 |
+
|
19 |
+
$base = $this->posts_controller->get_post_type_base( $this->post_type );
|
20 |
+
|
21 |
+
$query_params = $this->get_collection_params();
|
22 |
+
register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/terms/%s', $base, $this->taxonomy ), array(
|
23 |
+
array(
|
24 |
+
'methods' => WP_REST_Server::READABLE,
|
25 |
+
'callback' => array( $this, 'get_items' ),
|
26 |
+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
27 |
+
'args' => $query_params,
|
28 |
+
),
|
29 |
+
) );
|
30 |
+
|
31 |
+
register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/terms/%s/(?P<term_id>[\d]+)', $base, $this->taxonomy ), array(
|
32 |
+
array(
|
33 |
+
'methods' => WP_REST_Server::READABLE,
|
34 |
+
'callback' => array( $this, 'get_item' ),
|
35 |
+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
36 |
+
),
|
37 |
+
array(
|
38 |
+
'methods' => WP_REST_Server::CREATABLE,
|
39 |
+
'callback' => array( $this, 'create_item' ),
|
40 |
+
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
41 |
+
),
|
42 |
+
array(
|
43 |
+
'methods' => WP_REST_Server::DELETABLE,
|
44 |
+
'callback' => array( $this, 'delete_item' ),
|
45 |
+
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
46 |
+
),
|
47 |
+
) );
|
48 |
+
|
49 |
+
register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/terms/%s', $base, $this->taxonomy ) . '/schema', array(
|
50 |
+
'methods' => WP_REST_Server::READABLE,
|
51 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
52 |
+
) );
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Get all the terms that are attached to a post
|
57 |
+
*
|
58 |
+
* @param WP_REST_Request $request Full details about the request
|
59 |
+
* @return WP_Error|WP_REST_Response
|
60 |
+
*/
|
61 |
+
public function get_items( $request ) {
|
62 |
+
|
63 |
+
$post = get_post( absint( $request['post_id'] ) );
|
64 |
+
|
65 |
+
$is_request_valid = $this->validate_request( $request );
|
66 |
+
if ( is_wp_error( $is_request_valid ) ) {
|
67 |
+
return $is_request_valid;
|
68 |
+
}
|
69 |
+
|
70 |
+
$args = array(
|
71 |
+
'order' => $request['order'],
|
72 |
+
'orderby' => $request['orderby'],
|
73 |
+
);
|
74 |
+
$terms = wp_get_object_terms( $post->ID, $this->taxonomy, $args );
|
75 |
+
|
76 |
+
$response = array();
|
77 |
+
foreach ( $terms as $term ) {
|
78 |
+
$data = $this->terms_controller->prepare_item_for_response( $term, $request );
|
79 |
+
$response[] = $this->prepare_response_for_collection( $data );
|
80 |
+
}
|
81 |
+
|
82 |
+
$response = rest_ensure_response( $response );
|
83 |
+
|
84 |
+
return $response;
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Get a term that is attached to a post
|
89 |
+
*
|
90 |
+
* @param WP_REST_Request $request Full details about the request
|
91 |
+
* @return WP_Error|WP_REST_Response
|
92 |
+
*/
|
93 |
+
public function get_item( $request ) {
|
94 |
+
$post = get_post( absint( $request['post_id'] ) );
|
95 |
+
$term_id = absint( $request['term_id'] );
|
96 |
+
|
97 |
+
$is_request_valid = $this->validate_request( $request );
|
98 |
+
if ( is_wp_error( $is_request_valid ) ) {
|
99 |
+
return $is_request_valid;
|
100 |
+
}
|
101 |
+
|
102 |
+
$terms = wp_get_object_terms( $post->ID, $this->taxonomy );
|
103 |
+
|
104 |
+
if ( ! in_array( $term_id, wp_list_pluck( $terms, 'term_taxonomy_id' ) ) ) {
|
105 |
+
return new WP_Error( 'rest_post_not_in_term', __( 'Invalid taxonomy for post ID.' ), array( 'status' => 404 ) );
|
106 |
+
}
|
107 |
+
|
108 |
+
$term = $this->terms_controller->prepare_item_for_response( get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy ), $request );
|
109 |
+
|
110 |
+
$response = rest_ensure_response( $term );
|
111 |
+
|
112 |
+
return $response;
|
113 |
+
}
|
114 |
+
|
115 |
+
/**
|
116 |
+
* Add a term to a post
|
117 |
+
*
|
118 |
+
* @param WP_REST_Request $request Full details about the request
|
119 |
+
* @return WP_Error|WP_REST_Response
|
120 |
+
*/
|
121 |
+
public function create_item( $request ) {
|
122 |
+
$post = get_post( $request['post_id'] );
|
123 |
+
$term_id = absint( $request['term_id'] );
|
124 |
+
|
125 |
+
$is_request_valid = $this->validate_request( $request );
|
126 |
+
if ( is_wp_error( $is_request_valid ) ) {
|
127 |
+
return $is_request_valid;
|
128 |
+
}
|
129 |
+
|
130 |
+
$tt_ids = wp_set_object_terms( $post->ID, $term_id, $this->taxonomy, true );
|
131 |
+
|
132 |
+
if ( is_wp_error( $tt_ids ) ) {
|
133 |
+
return $tt_ids;
|
134 |
+
}
|
135 |
+
|
136 |
+
$term = $this->terms_controller->prepare_item_for_response( get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy ), $request );
|
137 |
+
|
138 |
+
$response = rest_ensure_response( $term );
|
139 |
+
$response->set_status( 201 );
|
140 |
+
|
141 |
+
return $term;
|
142 |
+
}
|
143 |
+
|
144 |
+
/**
|
145 |
+
* Remove a term from a post.
|
146 |
+
*
|
147 |
+
* @param WP_REST_Request $request Full details about the request
|
148 |
+
* @return WP_Error|null
|
149 |
+
*/
|
150 |
+
public function delete_item( $request ) {
|
151 |
+
$post = get_post( absint( $request['post_id'] ) );
|
152 |
+
$term_id = absint( $request['term_id'] );
|
153 |
+
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
|
154 |
+
|
155 |
+
// We don't support trashing for this type, error out
|
156 |
+
if ( ! $force ) {
|
157 |
+
return new WP_Error( 'rest_trash_not_supported', __( 'Terms do not support trashing.' ), array( 'status' => 501 ) );
|
158 |
+
}
|
159 |
+
|
160 |
+
$is_request_valid = $this->validate_request( $request );
|
161 |
+
if ( is_wp_error( $is_request_valid ) ) {
|
162 |
+
return $is_request_valid;
|
163 |
+
}
|
164 |
+
|
165 |
+
$previous_item = $this->get_item( $request );
|
166 |
+
|
167 |
+
$remove = wp_remove_object_terms( $post->ID, $term_id, $this->taxonomy );
|
168 |
+
|
169 |
+
if ( is_wp_error( $remove ) ) {
|
170 |
+
return $remove;
|
171 |
+
}
|
172 |
+
|
173 |
+
return $previous_item;
|
174 |
+
}
|
175 |
+
|
176 |
+
/**
|
177 |
+
* Get the Term schema, conforming to JSON Schema.
|
178 |
+
*
|
179 |
+
* @return array
|
180 |
+
*/
|
181 |
+
public function get_item_schema() {
|
182 |
+
return $this->terms_controller->get_item_schema();
|
183 |
+
}
|
184 |
+
|
185 |
+
/**
|
186 |
+
* Validate the API request for relationship requests.
|
187 |
+
*
|
188 |
+
* @param WP_REST_Request $request
|
189 |
+
* @return WP_Error|true
|
190 |
+
*/
|
191 |
+
protected function validate_request( $request ) {
|
192 |
+
|
193 |
+
$post_request = new WP_REST_Request();
|
194 |
+
$post_request->set_param( 'id', $request['post_id'] );
|
195 |
+
|
196 |
+
$post_check = $this->posts_controller->get_item( $post_request );
|
197 |
+
if ( is_wp_error( $post_check ) ) {
|
198 |
+
return $post_check;
|
199 |
+
}
|
200 |
+
|
201 |
+
if ( ! empty( $request['term_id'] ) ) {
|
202 |
+
$term_id = absint( $request['term_id'] );
|
203 |
+
|
204 |
+
if ( ! get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy ) ) {
|
205 |
+
return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
|
206 |
+
}
|
207 |
+
}
|
208 |
+
|
209 |
+
return true;
|
210 |
+
}
|
211 |
+
|
212 |
+
/**
|
213 |
+
* Check if a given request has access to read a post's term.
|
214 |
+
*
|
215 |
+
* @param WP_REST_Request $request Full details about the request.
|
216 |
+
* @return bool|WP_Error
|
217 |
+
*/
|
218 |
+
public function get_items_permissions_check( $request ) {
|
219 |
+
|
220 |
+
$post_request = new WP_REST_Request();
|
221 |
+
$post_request->set_param( 'id', $request['post_id'] );
|
222 |
+
|
223 |
+
$post_check = $this->posts_controller->get_item_permissions_check( $post_request );
|
224 |
+
|
225 |
+
if ( ! $post_check || is_wp_error( $post_check ) ) {
|
226 |
+
return $post_check;
|
227 |
+
}
|
228 |
+
|
229 |
+
$term_request = new WP_REST_Request();
|
230 |
+
$term_request->set_param( 'id', $request['term_id'] );
|
231 |
+
|
232 |
+
$terms_check = $this->terms_controller->get_item_permissions_check( $term_request );
|
233 |
+
|
234 |
+
if ( ! $terms_check || is_wp_error( $terms_check ) ) {
|
235 |
+
return $terms_check;
|
236 |
+
}
|
237 |
+
|
238 |
+
return true;
|
239 |
+
}
|
240 |
+
|
241 |
+
/**
|
242 |
+
* Check if a given request has access to create a post/term relationship.
|
243 |
+
*
|
244 |
+
* @param WP_REST_Request $request Full details about the request.
|
245 |
+
* @return bool|WP_Error
|
246 |
+
*/
|
247 |
+
public function create_item_permissions_check( $request ) {
|
248 |
+
|
249 |
+
$post_request = new WP_REST_Request();
|
250 |
+
$post_request->set_param( 'id', $request['post_id'] );
|
251 |
+
$post_check = $this->posts_controller->update_item_permissions_check( $post_request );
|
252 |
+
|
253 |
+
if ( ! $post_check || is_wp_error( $post_check ) ) {
|
254 |
+
return $post_check;
|
255 |
+
}
|
256 |
+
|
257 |
+
return true;
|
258 |
+
}
|
259 |
+
|
260 |
+
/**
|
261 |
+
* Get the query params for collections
|
262 |
+
*
|
263 |
+
* @return array
|
264 |
+
*/
|
265 |
+
public function get_collection_params() {
|
266 |
+
$query_params = array();
|
267 |
+
$query_params['order'] = array(
|
268 |
+
'description' => 'Order sort attribute ascending or descending.',
|
269 |
+
'type' => 'string',
|
270 |
+
'default' => 'asc',
|
271 |
+
'enum' => array( 'asc', 'desc' ),
|
272 |
+
);
|
273 |
+
$query_params['orderby'] = array(
|
274 |
+
'description' => 'Sort collection by object attribute.',
|
275 |
+
'type' => 'string',
|
276 |
+
'default' => 'name',
|
277 |
+
'enum' => array(
|
278 |
+
'count',
|
279 |
+
'name',
|
280 |
+
'slug',
|
281 |
+
'term_order',
|
282 |
+
),
|
283 |
+
);
|
284 |
+
return $query_params;
|
285 |
+
}
|
286 |
+
|
287 |
+
}
|
lib/endpoints/class-wp-rest-revisions-controller.php
ADDED
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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->parent_base = $this->parent_controller->get_post_type_base( $this->parent_post_type );
|
13 |
+
}
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Register routes for revisions based on post types supporting revisions
|
17 |
+
*/
|
18 |
+
public function register_routes() {
|
19 |
+
|
20 |
+
register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/revisions', array(
|
21 |
+
'methods' => WP_REST_Server::READABLE,
|
22 |
+
'callback' => array( $this, 'get_items' ),
|
23 |
+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
24 |
+
'args' => array(
|
25 |
+
'context' => array(
|
26 |
+
'default' => 'view',
|
27 |
+
),
|
28 |
+
),
|
29 |
+
) );
|
30 |
+
|
31 |
+
register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/revisions/(?P<id>[\d]+)', array(
|
32 |
+
array(
|
33 |
+
'methods' => WP_REST_Server::READABLE,
|
34 |
+
'callback' => array( $this, 'get_item' ),
|
35 |
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
36 |
+
'args' => array(
|
37 |
+
'context' => array(
|
38 |
+
'default' => 'view',
|
39 |
+
),
|
40 |
+
),
|
41 |
+
),
|
42 |
+
array(
|
43 |
+
'methods' => WP_REST_Server::DELETABLE,
|
44 |
+
'callback' => array( $this, 'delete_item' ),
|
45 |
+
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
46 |
+
),
|
47 |
+
));
|
48 |
+
|
49 |
+
register_rest_route( 'wp/v2', '/' . $this->parent_base . '/revisions/schema', array(
|
50 |
+
'methods' => WP_REST_Server::READABLE,
|
51 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
52 |
+
) );
|
53 |
+
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Get a collection of revisions
|
58 |
+
*
|
59 |
+
* @param WP_REST_Request $request Full data about the request.
|
60 |
+
* @return WP_Error|WP_REST_Response
|
61 |
+
*/
|
62 |
+
public function get_items( $request ) {
|
63 |
+
|
64 |
+
$parent = get_post( $request['parent_id'] );
|
65 |
+
if ( ! $request['parent_id'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
|
66 |
+
return new WP_Error( 'rest_post_invalid_parent_id', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) );
|
67 |
+
}
|
68 |
+
|
69 |
+
$revisions = wp_get_post_revisions( $request['parent_id'] );
|
70 |
+
|
71 |
+
$struct = array();
|
72 |
+
foreach ( $revisions as $revision ) {
|
73 |
+
$struct[] = $this->prepare_item_for_response( $revision, $request );
|
74 |
+
}
|
75 |
+
return $struct;
|
76 |
+
}
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Check if a given request has access to get revisions
|
80 |
+
*
|
81 |
+
* @param WP_REST_Request $request Full data about the request.
|
82 |
+
* @return WP_Error|bool
|
83 |
+
*/
|
84 |
+
public function get_items_permissions_check( $request ) {
|
85 |
+
|
86 |
+
$parent = get_post( $request['parent_id'] );
|
87 |
+
if ( ! $parent ) {
|
88 |
+
return true;
|
89 |
+
}
|
90 |
+
$parent_post_type_obj = get_post_type_object( $parent->post_type );
|
91 |
+
if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) {
|
92 |
+
return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot view revisions of this post.' ), array( 'status' => 403 ) );
|
93 |
+
}
|
94 |
+
|
95 |
+
return true;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Get one revision from the collection
|
100 |
+
*
|
101 |
+
* @param WP_REST_Request $request Full data about the request.
|
102 |
+
* @return WP_Error|array
|
103 |
+
*/
|
104 |
+
public function get_item( $request ) {
|
105 |
+
|
106 |
+
$parent = get_post( $request['parent_id'] );
|
107 |
+
if ( ! $request['parent_id'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
|
108 |
+
return new WP_Error( 'rest_post_invalid_parent_id', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) );
|
109 |
+
}
|
110 |
+
|
111 |
+
$revision = get_post( $request['id'] );
|
112 |
+
if ( ! $revision || 'revision' !== $revision->post_type ) {
|
113 |
+
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) );
|
114 |
+
}
|
115 |
+
|
116 |
+
$response = $this->prepare_item_for_response( $revision, $request );
|
117 |
+
return $response;
|
118 |
+
}
|
119 |
+
|
120 |
+
/**
|
121 |
+
* Check if a given request has access to get a specific revision
|
122 |
+
*
|
123 |
+
* @param WP_REST_Request $request Full data about the request.
|
124 |
+
* @return WP_Error|bool
|
125 |
+
*/
|
126 |
+
public function get_item_permissions_check( $request ) {
|
127 |
+
return $this->get_items_permissions_check( $request );
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Delete a single revision
|
132 |
+
*
|
133 |
+
* @param WP_REST_Request $request Full details about the request
|
134 |
+
* @return bool|WP_Error
|
135 |
+
*/
|
136 |
+
public function delete_item( $request ) {
|
137 |
+
$result = wp_delete_post( $request['id'], true );
|
138 |
+
if ( $result ) {
|
139 |
+
return true;
|
140 |
+
} else {
|
141 |
+
return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
|
142 |
+
}
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Check if a given request has access to delete a revision
|
147 |
+
*
|
148 |
+
* @param WP_REST_Request $request Full details about the request.
|
149 |
+
* @return bool|WP_Error
|
150 |
+
*/
|
151 |
+
public function delete_item_permissions_check( $request ) {
|
152 |
+
|
153 |
+
$response = $this->get_items_permissions_check( $request );
|
154 |
+
if ( ! $response || is_wp_error( $response ) ) {
|
155 |
+
return $response;
|
156 |
+
}
|
157 |
+
|
158 |
+
$post = get_post( $request['id'] );
|
159 |
+
$post_type = get_post_type_object( 'revision' );
|
160 |
+
return current_user_can( $post_type->cap->delete_post, $post->ID );
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Prepare the revision for the REST response
|
165 |
+
*
|
166 |
+
* @param mixed $item WordPress representation of the revision.
|
167 |
+
* @param WP_REST_Request $request Request object.
|
168 |
+
* @return array
|
169 |
+
*/
|
170 |
+
public function prepare_item_for_response( $post, $request ) {
|
171 |
+
|
172 |
+
// Base fields for every post
|
173 |
+
$data = array(
|
174 |
+
'author' => $post->post_author,
|
175 |
+
'date' => $this->prepare_date_response( $post->post_date_gmt, $post->post_date ),
|
176 |
+
'date_gmt' => $this->prepare_date_response( $post->post_date_gmt ),
|
177 |
+
'guid' => $post->guid,
|
178 |
+
'id' => $post->ID,
|
179 |
+
'modified' => $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ),
|
180 |
+
'modified_gmt' => $this->prepare_date_response( $post->post_modified_gmt ),
|
181 |
+
'parent' => (int) $post->post_parent,
|
182 |
+
'slug' => $post->post_name,
|
183 |
+
);
|
184 |
+
|
185 |
+
$schema = $this->get_item_schema();
|
186 |
+
|
187 |
+
if ( ! empty( $schema['properties']['title'] ) ) {
|
188 |
+
$data['title'] = $post->post_title;
|
189 |
+
}
|
190 |
+
|
191 |
+
if ( ! empty( $schema['properties']['content'] ) ) {
|
192 |
+
$data['content'] = $post->post_content;
|
193 |
+
}
|
194 |
+
|
195 |
+
if ( ! empty( $schema['properties']['excerpt'] ) ) {
|
196 |
+
$data['excerpt'] = $post->post_excerpt;
|
197 |
+
}
|
198 |
+
|
199 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
200 |
+
$data = $this->filter_response_by_context( $data, $context );
|
201 |
+
$data = $this->add_additional_fields_to_object( $data, $request );
|
202 |
+
$response = rest_ensure_response( $data );
|
203 |
+
if ( is_wp_error( $response ) ) {
|
204 |
+
return $response;
|
205 |
+
}
|
206 |
+
|
207 |
+
if ( ! empty( $data['parent'] ) ) {
|
208 |
+
$response->add_link( 'parent', rest_url( sprintf( 'wp/%s/%d', $this->parent_base, $data['parent'] ) ) );
|
209 |
+
}
|
210 |
+
|
211 |
+
return $response;
|
212 |
+
}
|
213 |
+
|
214 |
+
/**
|
215 |
+
* Check the post_date_gmt or modified_gmt and prepare any post or
|
216 |
+
* modified date for single post output.
|
217 |
+
*
|
218 |
+
* @param string $date_gmt
|
219 |
+
* @param string|null $date
|
220 |
+
* @return string|null ISO8601/RFC3339 formatted datetime.
|
221 |
+
*/
|
222 |
+
protected function prepare_date_response( $date_gmt, $date = null ) {
|
223 |
+
if ( '0000-00-00 00:00:00' === $date_gmt ) {
|
224 |
+
return null;
|
225 |
+
}
|
226 |
+
|
227 |
+
if ( isset( $date ) ) {
|
228 |
+
return rest_mysql_to_rfc3339( $date );
|
229 |
+
}
|
230 |
+
|
231 |
+
return rest_mysql_to_rfc3339( $date_gmt );
|
232 |
+
}
|
233 |
+
|
234 |
+
/**
|
235 |
+
* Get the revision's schema, conforming to JSON Schema
|
236 |
+
*
|
237 |
+
* @return array
|
238 |
+
*/
|
239 |
+
public function get_item_schema() {
|
240 |
+
$schema = array(
|
241 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
242 |
+
'title' => "{$this->parent_base}-revision",
|
243 |
+
'type' => 'object',
|
244 |
+
/*
|
245 |
+
* Base properties for every Revision
|
246 |
+
*/
|
247 |
+
'properties' => array(
|
248 |
+
'author' => array(
|
249 |
+
'description' => 'The ID for the author of the object.',
|
250 |
+
'type' => 'integer',
|
251 |
+
'context' => array( 'view' ),
|
252 |
+
),
|
253 |
+
'date' => array(
|
254 |
+
'description' => 'The date the object was published.',
|
255 |
+
'type' => 'string',
|
256 |
+
'format' => 'date-time',
|
257 |
+
'context' => array( 'view' ),
|
258 |
+
),
|
259 |
+
'date_gmt' => array(
|
260 |
+
'description' => 'The date the object was published, as GMT.',
|
261 |
+
'type' => 'string',
|
262 |
+
'format' => 'date-time',
|
263 |
+
'context' => array( 'view' ),
|
264 |
+
),
|
265 |
+
'guid' => array(
|
266 |
+
'description' => 'GUID for the object, as it exists in the database.',
|
267 |
+
'type' => 'string',
|
268 |
+
'context' => array( 'view' ),
|
269 |
+
),
|
270 |
+
'id' => array(
|
271 |
+
'description' => 'Unique identifier for the object.',
|
272 |
+
'type' => 'integer',
|
273 |
+
'context' => array( 'view' ),
|
274 |
+
),
|
275 |
+
'modified' => array(
|
276 |
+
'description' => 'The date the object was last modified.',
|
277 |
+
'type' => 'string',
|
278 |
+
'format' => 'date-time',
|
279 |
+
'context' => array( 'view' ),
|
280 |
+
),
|
281 |
+
'modified_gmt' => array(
|
282 |
+
'description' => 'The date the object was last modified, as GMT.',
|
283 |
+
'type' => 'string',
|
284 |
+
'format' => 'date-time',
|
285 |
+
'context' => array( 'view' ),
|
286 |
+
),
|
287 |
+
'parent' => array(
|
288 |
+
'description' => 'The ID for the parent of the object.',
|
289 |
+
'type' => 'integer',
|
290 |
+
'context' => array( 'view' ),
|
291 |
+
),
|
292 |
+
'slug' => array(
|
293 |
+
'description' => 'An alphanumeric identifier for the object unique to its type.',
|
294 |
+
'type' => 'string',
|
295 |
+
'context' => array( 'view' ),
|
296 |
+
),
|
297 |
+
),
|
298 |
+
);
|
299 |
+
|
300 |
+
$parent_schema = $this->parent_controller->get_item_schema();
|
301 |
+
|
302 |
+
foreach ( array( 'title', 'content', 'excerpt' ) as $property ) {
|
303 |
+
if ( empty( $parent_schema['properties'][ $property ] ) ) {
|
304 |
+
continue;
|
305 |
+
}
|
306 |
+
|
307 |
+
switch ( $property ) {
|
308 |
+
|
309 |
+
case 'title':
|
310 |
+
$schema['properties']['title'] = array(
|
311 |
+
'description' => 'Title for the object, as it exists in the database.',
|
312 |
+
'type' => 'string',
|
313 |
+
'context' => array( 'view' ),
|
314 |
+
);
|
315 |
+
break;
|
316 |
+
|
317 |
+
case 'content':
|
318 |
+
$schema['properties']['content'] = array(
|
319 |
+
'description' => 'Content for the object, as it exists in the database.',
|
320 |
+
'type' => 'string',
|
321 |
+
'context' => array( 'view' ),
|
322 |
+
);
|
323 |
+
break;
|
324 |
+
|
325 |
+
case 'excerpt':
|
326 |
+
$schema['properties']['excerpt'] = array(
|
327 |
+
'description' => 'Excerpt for the object, as it exists in the database.',
|
328 |
+
'type' => 'string',
|
329 |
+
'context' => array( 'view' ),
|
330 |
+
);
|
331 |
+
break;
|
332 |
+
|
333 |
+
}
|
334 |
+
}
|
335 |
+
|
336 |
+
return $this->add_additional_fields_schema( $schema );
|
337 |
+
}
|
338 |
+
|
339 |
+
}
|
lib/endpoints/class-wp-rest-taxonomies-controller.php
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Register the routes for the objects of the controller.
|
7 |
+
*/
|
8 |
+
public function register_routes() {
|
9 |
+
|
10 |
+
register_rest_route( 'wp/v2', '/taxonomies', array(
|
11 |
+
'methods' => WP_REST_Server::READABLE,
|
12 |
+
'callback' => array( $this, 'get_items' ),
|
13 |
+
'args' => array(
|
14 |
+
'post_type' => array(
|
15 |
+
'sanitize_callback' => 'sanitize_key',
|
16 |
+
),
|
17 |
+
),
|
18 |
+
) );
|
19 |
+
register_rest_route( 'wp/v2', '/taxonomies/schema', array(
|
20 |
+
'methods' => WP_REST_Server::READABLE,
|
21 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
22 |
+
) );
|
23 |
+
register_rest_route( 'wp/v2', '/taxonomies/(?P<taxonomy>[\w-]+)', array(
|
24 |
+
'methods' => WP_REST_Server::READABLE,
|
25 |
+
'callback' => array( $this, 'get_item' ),
|
26 |
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
27 |
+
) );
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Get all public taxonomies
|
32 |
+
*
|
33 |
+
* @param WP_REST_Request $request
|
34 |
+
* @return array
|
35 |
+
*/
|
36 |
+
public function get_items( $request ) {
|
37 |
+
if ( ! empty( $request['post_type'] ) ) {
|
38 |
+
$taxonomies = get_object_taxonomies( $request['post_type'], 'objects' );
|
39 |
+
} else {
|
40 |
+
$taxonomies = get_taxonomies( '', 'objects' );
|
41 |
+
}
|
42 |
+
$data = array();
|
43 |
+
foreach ( $taxonomies as $tax_type => $value ) {
|
44 |
+
$tax = $this->prepare_item_for_response( $value, $request );
|
45 |
+
if ( is_wp_error( $tax ) ) {
|
46 |
+
continue;
|
47 |
+
}
|
48 |
+
$data[] = $tax;
|
49 |
+
}
|
50 |
+
return $data;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Get a specific taxonomy
|
55 |
+
*
|
56 |
+
* @param WP_REST_Request $request
|
57 |
+
* @return array|WP_Error
|
58 |
+
*/
|
59 |
+
public function get_item( $request ) {
|
60 |
+
$tax_obj = get_taxonomy( $request['taxonomy'] );
|
61 |
+
if ( empty( $tax_obj ) ) {
|
62 |
+
return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid taxonomy.' ), array( 'status' => 404 ) );
|
63 |
+
}
|
64 |
+
return $this->prepare_item_for_response( $tax_obj, $request );
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Check if a given request has access a taxonomy
|
69 |
+
*
|
70 |
+
* @param WP_REST_Request $request Full details about the request.
|
71 |
+
* @return bool
|
72 |
+
*/
|
73 |
+
public function get_item_permissions_check( $request ) {
|
74 |
+
|
75 |
+
$tax_obj = get_taxonomy( $request['taxonomy'] );
|
76 |
+
|
77 |
+
if ( $tax_obj && empty( $tax_obj->show_in_rest ) ) {
|
78 |
+
return false;
|
79 |
+
}
|
80 |
+
|
81 |
+
return true;
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Prepare a taxonomy object for serialization
|
86 |
+
*
|
87 |
+
* @param stdClass $taxonomy Taxonomy data
|
88 |
+
* @param WP_REST_Request $request
|
89 |
+
* @return array Taxonomy data
|
90 |
+
*/
|
91 |
+
public function prepare_item_for_response( $taxonomy, $request ) {
|
92 |
+
if ( empty( $taxonomy->show_in_rest ) ) {
|
93 |
+
return new WP_Error( 'rest_cannot_read_taxonomy', __( 'Cannot view taxonomy' ), array( 'status' => 403 ) );
|
94 |
+
}
|
95 |
+
|
96 |
+
$data = array(
|
97 |
+
'name' => $taxonomy->label,
|
98 |
+
'slug' => $taxonomy->name,
|
99 |
+
'description' => $taxonomy->description,
|
100 |
+
'labels' => $taxonomy->labels,
|
101 |
+
'types' => $taxonomy->object_type,
|
102 |
+
'show_cloud' => $taxonomy->show_tagcloud,
|
103 |
+
'hierarchical' => $taxonomy->hierarchical,
|
104 |
+
);
|
105 |
+
|
106 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
107 |
+
$data = $this->filter_response_by_context( $data, $context );
|
108 |
+
$data = $this->add_additional_fields_to_object( $data, $request );
|
109 |
+
|
110 |
+
return apply_filters( 'rest_prepare_taxonomy', $data, $taxonomy, $request );
|
111 |
+
}
|
112 |
+
|
113 |
+
/**
|
114 |
+
* Get the taxonomy's schema, conforming to JSON Schema
|
115 |
+
*
|
116 |
+
* @return array
|
117 |
+
*/
|
118 |
+
public function get_item_schema() {
|
119 |
+
$schema = array(
|
120 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
121 |
+
'title' => 'taxonomy',
|
122 |
+
'type' => 'object',
|
123 |
+
'properties' => array(
|
124 |
+
'description' => array(
|
125 |
+
'description' => 'A human-readable description of the object.',
|
126 |
+
'type' => 'string',
|
127 |
+
'context' => array( 'view' ),
|
128 |
+
),
|
129 |
+
'hierarchical' => array(
|
130 |
+
'description' => 'Whether or not the type should have children.',
|
131 |
+
'type' => 'boolean',
|
132 |
+
'context' => array( 'view' ),
|
133 |
+
),
|
134 |
+
'labels' => array(
|
135 |
+
'description' => 'Human-readable labels for the type for various contexts.',
|
136 |
+
'type' => 'object',
|
137 |
+
'context' => array( 'view' ),
|
138 |
+
),
|
139 |
+
'name' => array(
|
140 |
+
'description' => 'The title for the object.',
|
141 |
+
'type' => 'string',
|
142 |
+
'context' => array( 'view' ),
|
143 |
+
),
|
144 |
+
'slug' => array(
|
145 |
+
'description' => 'An alphanumeric identifier for the object.',
|
146 |
+
'type' => 'string',
|
147 |
+
'context' => array( 'view' ),
|
148 |
+
),
|
149 |
+
'show_cloud' => array(
|
150 |
+
'description' => 'Whether or not the term cloud should be displayed.',
|
151 |
+
'type' => 'boolean',
|
152 |
+
'context' => array( 'view' ),
|
153 |
+
),
|
154 |
+
'types' => array(
|
155 |
+
'description' => 'Types associated with taxonomy.',
|
156 |
+
'type' => 'array',
|
157 |
+
'context' => array( 'view' ),
|
158 |
+
),
|
159 |
+
),
|
160 |
+
);
|
161 |
+
return $this->add_additional_fields_schema( $schema );
|
162 |
+
}
|
163 |
+
|
164 |
+
}
|
lib/endpoints/class-wp-rest-terms-controller.php
ADDED
@@ -0,0 +1,595 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Access terms associated with a taxonomy
|
5 |
+
*/
|
6 |
+
class WP_REST_Terms_Controller extends WP_REST_Controller {
|
7 |
+
|
8 |
+
protected $taxonomy;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @param string $taxonomy
|
12 |
+
*/
|
13 |
+
public function __construct( $taxonomy ) {
|
14 |
+
$this->taxonomy = $taxonomy;
|
15 |
+
}
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Register the routes for the objects of the controller.
|
19 |
+
*/
|
20 |
+
public function register_routes() {
|
21 |
+
|
22 |
+
$base = $this->get_taxonomy_base( $this->taxonomy );
|
23 |
+
$query_params = $this->get_collection_params();
|
24 |
+
register_rest_route( 'wp/v2', '/terms/' . $base, array(
|
25 |
+
array(
|
26 |
+
'methods' => WP_REST_Server::READABLE,
|
27 |
+
'callback' => array( $this, 'get_items' ),
|
28 |
+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
29 |
+
'args' => $query_params,
|
30 |
+
),
|
31 |
+
array(
|
32 |
+
'methods' => WP_REST_Server::CREATABLE,
|
33 |
+
'callback' => array( $this, 'create_item' ),
|
34 |
+
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
35 |
+
'args' => array(
|
36 |
+
'name' => array(
|
37 |
+
'required' => true,
|
38 |
+
'sanitize_callback' => 'sanitize_text_field',
|
39 |
+
),
|
40 |
+
'description' => array(
|
41 |
+
'sanitize_callback' => 'wp_filter_post_kses',
|
42 |
+
),
|
43 |
+
'slug' => array(
|
44 |
+
'sanitize_callback' => 'sanitize_title',
|
45 |
+
),
|
46 |
+
'parent' => array(),
|
47 |
+
),
|
48 |
+
),
|
49 |
+
));
|
50 |
+
register_rest_route( 'wp/v2', '/terms/' . $base . '/(?P<id>[\d]+)', array(
|
51 |
+
array(
|
52 |
+
'methods' => WP_REST_Server::READABLE,
|
53 |
+
'callback' => array( $this, 'get_item' ),
|
54 |
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
55 |
+
),
|
56 |
+
array(
|
57 |
+
'methods' => WP_REST_Server::EDITABLE,
|
58 |
+
'callback' => array( $this, 'update_item' ),
|
59 |
+
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
60 |
+
'args' => array(
|
61 |
+
'name' => array(
|
62 |
+
'sanitize_callback' => 'sanitize_text_field',
|
63 |
+
),
|
64 |
+
'description' => array(
|
65 |
+
'sanitize_callback' => 'wp_filter_post_kses',
|
66 |
+
),
|
67 |
+
'slug' => array(
|
68 |
+
'sanitize_callback' => 'sanitize_title',
|
69 |
+
),
|
70 |
+
'parent' => array(),
|
71 |
+
),
|
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 |
+
),
|
78 |
+
) );
|
79 |
+
register_rest_route( 'wp/v2', '/terms/' . $base . '/schema', array(
|
80 |
+
'methods' => WP_REST_Server::READABLE,
|
81 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
82 |
+
) );
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Get terms associated with a taxonomy
|
87 |
+
*
|
88 |
+
* @param WP_REST_Request $request Full details about the request
|
89 |
+
* @return WP_REST_Response|WP_Error
|
90 |
+
*/
|
91 |
+
public function get_items( $request ) {
|
92 |
+
$prepared_args = array( 'hide_empty' => false );
|
93 |
+
|
94 |
+
$prepared_args['number'] = $request['per_page'];
|
95 |
+
$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
|
96 |
+
$prepared_args['search'] = $request['search'];
|
97 |
+
$prepared_args['order'] = $request['order'];
|
98 |
+
$prepared_args['orderby'] = $request['orderby'];
|
99 |
+
|
100 |
+
$taxonomy_obj = get_taxonomy( $this->taxonomy );
|
101 |
+
if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) {
|
102 |
+
$parent = get_term_by( 'term_taxonomy_id', (int) $request['parent'], $this->taxonomy );
|
103 |
+
if ( $parent ) {
|
104 |
+
$prepared_args['parent'] = $parent->term_id;
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
$query_result = get_terms( $this->taxonomy, $prepared_args );
|
109 |
+
$response = array();
|
110 |
+
foreach ( $query_result as $term ) {
|
111 |
+
$data = $this->prepare_item_for_response( $term, $request );
|
112 |
+
$response[] = $this->prepare_response_for_collection( $data );
|
113 |
+
}
|
114 |
+
|
115 |
+
$response = rest_ensure_response( $response );
|
116 |
+
unset( $prepared_args['number'] );
|
117 |
+
unset( $prepared_args['offset'] );
|
118 |
+
$total_terms = wp_count_terms( $this->taxonomy, $prepared_args );
|
119 |
+
$response->header( 'X-WP-Total', (int) $total_terms );
|
120 |
+
$max_pages = ceil( $total_terms / $request['per_page'] );
|
121 |
+
$response->header( 'X-WP-TotalPages', (int) $max_pages );
|
122 |
+
|
123 |
+
$base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/terms/' . $this->get_taxonomy_base( $this->taxonomy ) ) );
|
124 |
+
if ( $request['page'] > 1 ) {
|
125 |
+
$prev_page = $request['page'] - 1;
|
126 |
+
if ( $prev_page > $max_pages ) {
|
127 |
+
$prev_page = $max_pages;
|
128 |
+
}
|
129 |
+
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
130 |
+
$response->link_header( 'prev', $prev_link );
|
131 |
+
}
|
132 |
+
if ( $max_pages > $request['page'] ) {
|
133 |
+
$next_page = $request['page'] + 1;
|
134 |
+
$next_link = add_query_arg( 'page', $next_page, $base );
|
135 |
+
$response->link_header( 'next', $next_link );
|
136 |
+
}
|
137 |
+
|
138 |
+
return $response;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Get a single term from a taxonomy
|
143 |
+
*
|
144 |
+
* @param WP_REST_Request $request Full details about the request
|
145 |
+
* @return WP_REST_Request|WP_Error
|
146 |
+
*/
|
147 |
+
public function get_item( $request ) {
|
148 |
+
|
149 |
+
$term = get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy );
|
150 |
+
if ( ! $term ) {
|
151 |
+
return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
|
152 |
+
}
|
153 |
+
if ( is_wp_error( $term ) ) {
|
154 |
+
return $term;
|
155 |
+
}
|
156 |
+
|
157 |
+
$response = $this->prepare_item_for_response( $term, $request );
|
158 |
+
|
159 |
+
return rest_ensure_response( $response );
|
160 |
+
}
|
161 |
+
|
162 |
+
/**
|
163 |
+
* Create a single term for a taxonomy
|
164 |
+
*
|
165 |
+
* @param WP_REST_Request $request Full details about the request
|
166 |
+
* @return WP_REST_Request|WP_Error
|
167 |
+
*/
|
168 |
+
public function create_item( $request ) {
|
169 |
+
$name = $request['name'];
|
170 |
+
|
171 |
+
$args = array();
|
172 |
+
|
173 |
+
if ( isset( $request['description'] ) ) {
|
174 |
+
$args['description'] = $request['description'];
|
175 |
+
}
|
176 |
+
if ( isset( $request['slug'] ) ) {
|
177 |
+
$args['slug'] = $request['slug'];
|
178 |
+
}
|
179 |
+
|
180 |
+
if ( isset( $request['parent'] ) ) {
|
181 |
+
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
|
182 |
+
return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set term parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
|
183 |
+
}
|
184 |
+
|
185 |
+
$parent = get_term_by( 'term_taxonomy_id', (int) $request['parent'], $this->taxonomy );
|
186 |
+
|
187 |
+
if ( ! $parent ) {
|
188 |
+
return new WP_Error( 'rest_term_invalid', __( "Parent term doesn't exist." ), array( 'status' => 404 ) );
|
189 |
+
}
|
190 |
+
|
191 |
+
$args['parent'] = $parent->term_id;
|
192 |
+
}
|
193 |
+
|
194 |
+
$term = wp_insert_term( $name, $this->taxonomy, $args );
|
195 |
+
if ( is_wp_error( $term ) ) {
|
196 |
+
return $term;
|
197 |
+
}
|
198 |
+
|
199 |
+
$this->update_additional_fields_for_object( $term, $request );
|
200 |
+
|
201 |
+
$response = $this->get_item( array(
|
202 |
+
'id' => $term['term_taxonomy_id'],
|
203 |
+
) );
|
204 |
+
|
205 |
+
return rest_ensure_response( $response );
|
206 |
+
}
|
207 |
+
|
208 |
+
/**
|
209 |
+
* Update a single term from a taxonomy
|
210 |
+
*
|
211 |
+
* @param WP_REST_Request $request Full details about the request
|
212 |
+
* @return WP_REST_Request|WP_Error
|
213 |
+
*/
|
214 |
+
public function update_item( $request ) {
|
215 |
+
|
216 |
+
$prepared_args = array();
|
217 |
+
if ( isset( $request['name'] ) ) {
|
218 |
+
$prepared_args['name'] = $request['name'];
|
219 |
+
}
|
220 |
+
if ( isset( $request['description'] ) ) {
|
221 |
+
$prepared_args['description'] = $request['description'];
|
222 |
+
}
|
223 |
+
if ( isset( $request['slug'] ) ) {
|
224 |
+
$prepared_args['slug'] = $request['slug'];
|
225 |
+
}
|
226 |
+
|
227 |
+
if ( isset( $request['parent'] ) ) {
|
228 |
+
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
|
229 |
+
return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set term parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
|
230 |
+
}
|
231 |
+
|
232 |
+
$parent = get_term_by( 'term_taxonomy_id', (int) $request['parent'], $this->taxonomy );
|
233 |
+
|
234 |
+
if ( ! $parent ) {
|
235 |
+
return new WP_Error( 'rest_term_invalid', __( "Parent term doesn't exist." ), array( 'status' => 400 ) );
|
236 |
+
}
|
237 |
+
|
238 |
+
$prepared_args['parent'] = $parent->term_id;
|
239 |
+
}
|
240 |
+
|
241 |
+
$term = get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy );
|
242 |
+
if ( ! $term ) {
|
243 |
+
return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
|
244 |
+
}
|
245 |
+
|
246 |
+
// Only update the term if we haz something to update.
|
247 |
+
if ( ! empty( $prepared_args ) ) {
|
248 |
+
$update = wp_update_term( $term->term_id, $term->taxonomy, $prepared_args );
|
249 |
+
if ( is_wp_error( $update ) ) {
|
250 |
+
return $update;
|
251 |
+
}
|
252 |
+
}
|
253 |
+
|
254 |
+
$this->update_additional_fields_for_object( get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy ), $request );
|
255 |
+
|
256 |
+
$response = $this->get_item( array(
|
257 |
+
'id' => $term->term_taxonomy_id,
|
258 |
+
) );
|
259 |
+
|
260 |
+
return rest_ensure_response( $response );
|
261 |
+
}
|
262 |
+
|
263 |
+
/**
|
264 |
+
* Delete a single term from a taxonomy
|
265 |
+
*
|
266 |
+
* @param WP_REST_Request $request Full details about the request
|
267 |
+
* @return null
|
268 |
+
*/
|
269 |
+
public function delete_item( $request ) {
|
270 |
+
|
271 |
+
// Get the actual term_id
|
272 |
+
$term = get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy );
|
273 |
+
$get_request = new WP_REST_Request( 'GET', rest_url( 'wp/v2/terms/' . $this->get_taxonomy_base( $term->taxonomy ) . '/' . (int) $request['id'] ) );
|
274 |
+
$get_request->set_param( 'context', 'view' );
|
275 |
+
$response = $this->prepare_item_for_response( $term, $get_request );
|
276 |
+
|
277 |
+
$retval = wp_delete_term( $term->term_id, $term->taxonomy );
|
278 |
+
if ( ! $retval ) {
|
279 |
+
return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) );
|
280 |
+
}
|
281 |
+
|
282 |
+
return $response;
|
283 |
+
}
|
284 |
+
|
285 |
+
/**
|
286 |
+
* Check if a given request has access to read the terms.
|
287 |
+
*
|
288 |
+
* @param WP_REST_Request $request Full details about the request.
|
289 |
+
* @return bool|WP_Error
|
290 |
+
*/
|
291 |
+
public function get_items_permissions_check( $request ) {
|
292 |
+
|
293 |
+
$valid = $this->check_valid_taxonomy( $this->taxonomy );
|
294 |
+
if ( is_wp_error( $valid ) ) {
|
295 |
+
return $valid;
|
296 |
+
}
|
297 |
+
|
298 |
+
$tax_obj = get_taxonomy( $this->taxonomy );
|
299 |
+
if ( $tax_obj && false === $tax_obj->public ) {
|
300 |
+
return false;
|
301 |
+
}
|
302 |
+
|
303 |
+
return true;
|
304 |
+
}
|
305 |
+
|
306 |
+
/**
|
307 |
+
* Check if a given request has access to read a term.
|
308 |
+
*
|
309 |
+
* @param WP_REST_Request $request Full details about the request.
|
310 |
+
* @return bool|WP_Error
|
311 |
+
*/
|
312 |
+
public function get_item_permissions_check( $request ) {
|
313 |
+
|
314 |
+
$valid = $this->check_valid_taxonomy( $this->taxonomy );
|
315 |
+
if ( is_wp_error( $valid ) ) {
|
316 |
+
return $valid;
|
317 |
+
}
|
318 |
+
|
319 |
+
$tax_obj = get_taxonomy( $this->taxonomy );
|
320 |
+
if ( $tax_obj && false === $tax_obj->public ) {
|
321 |
+
return false;
|
322 |
+
}
|
323 |
+
|
324 |
+
return true;
|
325 |
+
}
|
326 |
+
|
327 |
+
|
328 |
+
/**
|
329 |
+
* Check if a given request has access to create a term
|
330 |
+
*
|
331 |
+
* @param WP_REST_Request $request Full details about the request.
|
332 |
+
* @return bool|WP_Error
|
333 |
+
*/
|
334 |
+
public function create_item_permissions_check( $request ) {
|
335 |
+
|
336 |
+
$valid = $this->check_valid_taxonomy( $this->taxonomy );
|
337 |
+
if ( is_wp_error( $valid ) ) {
|
338 |
+
return $valid;
|
339 |
+
}
|
340 |
+
|
341 |
+
$taxonomy_obj = get_taxonomy( $this->taxonomy );
|
342 |
+
if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
|
343 |
+
return false;
|
344 |
+
}
|
345 |
+
|
346 |
+
return true;
|
347 |
+
}
|
348 |
+
|
349 |
+
/**
|
350 |
+
* Check if a given request has access to update a term
|
351 |
+
*
|
352 |
+
* @param WP_REST_Request $request Full details about the request.
|
353 |
+
* @return bool|WP_Error
|
354 |
+
*/
|
355 |
+
public function update_item_permissions_check( $request ) {
|
356 |
+
|
357 |
+
$valid = $this->check_valid_taxonomy( $this->taxonomy );
|
358 |
+
if ( is_wp_error( $valid ) ) {
|
359 |
+
return $valid;
|
360 |
+
}
|
361 |
+
|
362 |
+
$taxonomy_obj = get_taxonomy( $this->taxonomy );
|
363 |
+
if ( $taxonomy_obj && ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
|
364 |
+
return false;
|
365 |
+
}
|
366 |
+
|
367 |
+
return true;
|
368 |
+
}
|
369 |
+
|
370 |
+
/**
|
371 |
+
* Check if a given request has access to delete a term
|
372 |
+
*
|
373 |
+
* @param WP_REST_Request $request Full details about the request.
|
374 |
+
* @return bool|WP_Error
|
375 |
+
*/
|
376 |
+
public function delete_item_permissions_check( $request ) {
|
377 |
+
|
378 |
+
$valid = $this->check_valid_taxonomy( $this->taxonomy );
|
379 |
+
if ( is_wp_error( $valid ) ) {
|
380 |
+
return $valid;
|
381 |
+
}
|
382 |
+
|
383 |
+
$term = get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy );
|
384 |
+
if ( ! $term ) {
|
385 |
+
return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
|
386 |
+
}
|
387 |
+
|
388 |
+
$taxonomy_obj = get_taxonomy( $this->taxonomy );
|
389 |
+
if ( $taxonomy_obj && ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) {
|
390 |
+
return false;
|
391 |
+
}
|
392 |
+
|
393 |
+
return true;
|
394 |
+
}
|
395 |
+
|
396 |
+
/**
|
397 |
+
* Get the base path for a term's taxonomy endpoints.
|
398 |
+
*
|
399 |
+
* @param object|string $taxonomy
|
400 |
+
* @return string $base
|
401 |
+
*/
|
402 |
+
public function get_taxonomy_base( $taxonomy ) {
|
403 |
+
if ( ! is_object( $taxonomy ) ) {
|
404 |
+
$taxonomy = get_taxonomy( $taxonomy );
|
405 |
+
}
|
406 |
+
|
407 |
+
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
|
408 |
+
|
409 |
+
return $base;
|
410 |
+
}
|
411 |
+
|
412 |
+
/**
|
413 |
+
* Prepare a single term output for response
|
414 |
+
*
|
415 |
+
* @param obj $item Term object
|
416 |
+
* @param WP_REST_Request $request
|
417 |
+
*/
|
418 |
+
public function prepare_item_for_response( $item, $request ) {
|
419 |
+
|
420 |
+
$parent_id = 0;
|
421 |
+
if ( $item->parent ) {
|
422 |
+
$parent_term = get_term_by( 'id', (int) $item->parent, $item->taxonomy );
|
423 |
+
if ( $parent_term ) {
|
424 |
+
$parent_id = $parent_term->term_taxonomy_id;
|
425 |
+
}
|
426 |
+
}
|
427 |
+
|
428 |
+
$data = array(
|
429 |
+
'id' => (int) $item->term_taxonomy_id,
|
430 |
+
'count' => (int) $item->count,
|
431 |
+
'description' => $item->description,
|
432 |
+
'link' => get_term_link( $item ),
|
433 |
+
'name' => $item->name,
|
434 |
+
'slug' => $item->slug,
|
435 |
+
'taxonomy' => $item->taxonomy,
|
436 |
+
);
|
437 |
+
$schema = $this->get_item_schema();
|
438 |
+
if ( ! empty( $schema['properties']['parent'] ) ) {
|
439 |
+
$data['parent'] = (int) $parent_id;
|
440 |
+
}
|
441 |
+
|
442 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
443 |
+
$data = $this->filter_response_by_context( $data, $context );
|
444 |
+
$data = $this->add_additional_fields_to_object( $data, $request );
|
445 |
+
|
446 |
+
$data = rest_ensure_response( $data );
|
447 |
+
|
448 |
+
$data->add_links( $this->prepare_links( $item ) );
|
449 |
+
|
450 |
+
return apply_filters( 'rest_prepare_term', $data, $item, $request );
|
451 |
+
}
|
452 |
+
|
453 |
+
/**
|
454 |
+
* Prepare links for the request.
|
455 |
+
*
|
456 |
+
* @param object $term Term object.
|
457 |
+
* @return array Links for the given term.
|
458 |
+
*/
|
459 |
+
protected function prepare_links( $term ) {
|
460 |
+
$base = '/wp/v2/terms/' . $this->get_taxonomy_base( $term->taxonomy );
|
461 |
+
$links = array(
|
462 |
+
'self' => array(
|
463 |
+
'href' => rest_url( trailingslashit( $base ) . $term->term_taxonomy_id ),
|
464 |
+
),
|
465 |
+
'collection' => array(
|
466 |
+
'href' => rest_url( $base ),
|
467 |
+
),
|
468 |
+
);
|
469 |
+
|
470 |
+
if ( $term->parent ) {
|
471 |
+
$parent_term = get_term_by( 'id', (int) $term->parent, $term->taxonomy );
|
472 |
+
if ( $parent_term ) {
|
473 |
+
$links['up'] = array(
|
474 |
+
'href' => rest_url( sprintf( 'wp/v2/terms/%s/%d', $this->get_taxonomy_base( $parent_term->taxonomy ), $parent_term->term_taxonomy_id ) ),
|
475 |
+
'embeddable' => true,
|
476 |
+
);
|
477 |
+
}
|
478 |
+
}
|
479 |
+
|
480 |
+
return $links;
|
481 |
+
}
|
482 |
+
|
483 |
+
/**
|
484 |
+
* Get the Term's schema, conforming to JSON Schema
|
485 |
+
*
|
486 |
+
* @return array
|
487 |
+
*/
|
488 |
+
public function get_item_schema() {
|
489 |
+
$schema = array(
|
490 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
491 |
+
'title' => 'term',
|
492 |
+
'type' => 'object',
|
493 |
+
'properties' => array(
|
494 |
+
'id' => array(
|
495 |
+
'description' => 'Unique identifier for the object.',
|
496 |
+
'type' => 'integer',
|
497 |
+
'context' => array( 'view', 'embed' ),
|
498 |
+
'readonly' => true,
|
499 |
+
),
|
500 |
+
'count' => array(
|
501 |
+
'description' => 'Number of published posts for the object.',
|
502 |
+
'type' => 'integer',
|
503 |
+
'context' => array( 'view' ),
|
504 |
+
'readonly' => true,
|
505 |
+
),
|
506 |
+
'description' => array(
|
507 |
+
'description' => 'A human-readable description of the object.',
|
508 |
+
'type' => 'string',
|
509 |
+
'context' => array( 'view' ),
|
510 |
+
),
|
511 |
+
'link' => array(
|
512 |
+
'description' => 'URL to the object.',
|
513 |
+
'type' => 'string',
|
514 |
+
'format' => 'uri',
|
515 |
+
'context' => array( 'view', 'embed' ),
|
516 |
+
'readonly' => true,
|
517 |
+
),
|
518 |
+
'name' => array(
|
519 |
+
'description' => 'The title for the object.',
|
520 |
+
'type' => 'string',
|
521 |
+
'context' => array( 'view', 'embed' ),
|
522 |
+
),
|
523 |
+
'slug' => array(
|
524 |
+
'description' => 'An alphanumeric identifier for the object unique to its type.',
|
525 |
+
'type' => 'string',
|
526 |
+
'context' => array( 'view', 'embed' ),
|
527 |
+
),
|
528 |
+
'taxonomy' => array(
|
529 |
+
'description' => 'Type attribution for the object.',
|
530 |
+
'type' => 'string',
|
531 |
+
'enum' => array_keys( get_taxonomies() ),
|
532 |
+
'context' => array( 'view', 'embed' ),
|
533 |
+
'readonly' => true,
|
534 |
+
),
|
535 |
+
),
|
536 |
+
);
|
537 |
+
$taxonomy = get_taxonomy( $this->taxonomy );
|
538 |
+
if ( $taxonomy->hierarchical ) {
|
539 |
+
$schema['properties']['parent'] = array(
|
540 |
+
'description' => 'The ID for the parent of the object.',
|
541 |
+
'type' => 'integer',
|
542 |
+
'context' => array( 'view' ),
|
543 |
+
);
|
544 |
+
}
|
545 |
+
return $this->add_additional_fields_schema( $schema );
|
546 |
+
}
|
547 |
+
|
548 |
+
/**
|
549 |
+
* Get the query params for collections
|
550 |
+
*
|
551 |
+
* @return array
|
552 |
+
*/
|
553 |
+
public function get_collection_params() {
|
554 |
+
$query_params = parent::get_collection_params();
|
555 |
+
$query_params['order'] = array(
|
556 |
+
'description' => 'Order sort attribute ascending or descending.',
|
557 |
+
'type' => 'string',
|
558 |
+
'default' => 'asc',
|
559 |
+
'enum' => array( 'asc', 'desc' ),
|
560 |
+
);
|
561 |
+
$query_params['orderby'] = array(
|
562 |
+
'description' => 'Sort collection by object attribute.',
|
563 |
+
'type' => 'string',
|
564 |
+
'default' => 'name',
|
565 |
+
'enum' => array(
|
566 |
+
'id',
|
567 |
+
'name',
|
568 |
+
'slug',
|
569 |
+
),
|
570 |
+
);
|
571 |
+
$taxonomy = get_taxonomy( $this->taxonomy );
|
572 |
+
if ( $taxonomy->hierarchical ) {
|
573 |
+
$query_params['parent'] = array(
|
574 |
+
'description' => 'Limit result set to terms assigned to a specific parent term.',
|
575 |
+
'type' => 'integer',
|
576 |
+
'sanitize_callback' => 'absint',
|
577 |
+
);
|
578 |
+
}
|
579 |
+
return $query_params;
|
580 |
+
}
|
581 |
+
|
582 |
+
/**
|
583 |
+
* Check that the taxonomy is valid
|
584 |
+
*
|
585 |
+
* @param string
|
586 |
+
* @return bool|WP_Error
|
587 |
+
*/
|
588 |
+
protected function check_valid_taxonomy( $taxonomy ) {
|
589 |
+
if ( get_taxonomy( $taxonomy ) ) {
|
590 |
+
return true;
|
591 |
+
}
|
592 |
+
|
593 |
+
return new WP_Error( 'rest_taxonomy_invalid', __( "Taxonomy doesn't exist" ), array( 'status' => 404 ) );
|
594 |
+
}
|
595 |
+
}
|
lib/endpoints/class-wp-rest-users-controller.php
ADDED
@@ -0,0 +1,751 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Access users
|
5 |
+
*/
|
6 |
+
class WP_REST_Users_Controller extends WP_REST_Controller {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Register the routes for the objects of the controller.
|
10 |
+
*/
|
11 |
+
public function register_routes() {
|
12 |
+
|
13 |
+
$query_params = $this->get_collection_params();
|
14 |
+
register_rest_route( 'wp/v2', '/users', array(
|
15 |
+
array(
|
16 |
+
'methods' => WP_REST_Server::READABLE,
|
17 |
+
'callback' => array( $this, 'get_items' ),
|
18 |
+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
19 |
+
'args' => $query_params,
|
20 |
+
),
|
21 |
+
array(
|
22 |
+
'methods' => WP_REST_Server::CREATABLE,
|
23 |
+
'callback' => array( $this, 'create_item' ),
|
24 |
+
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
25 |
+
'args' => array_merge( $this->get_endpoint_args_for_item_schema( true ), array(
|
26 |
+
'password' => array(
|
27 |
+
'required' => true,
|
28 |
+
),
|
29 |
+
) ),
|
30 |
+
),
|
31 |
+
) );
|
32 |
+
register_rest_route( 'wp/v2', '/users/(?P<id>[\d]+)', array(
|
33 |
+
array(
|
34 |
+
'methods' => WP_REST_Server::READABLE,
|
35 |
+
'callback' => array( $this, 'get_item' ),
|
36 |
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
37 |
+
'args' => array(
|
38 |
+
'context' => array(
|
39 |
+
'default' => 'embed',
|
40 |
+
),
|
41 |
+
),
|
42 |
+
),
|
43 |
+
array(
|
44 |
+
'methods' => WP_REST_Server::EDITABLE,
|
45 |
+
'callback' => array( $this, 'update_item' ),
|
46 |
+
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
47 |
+
'args' => array_merge( $this->get_endpoint_args_for_item_schema( false ), array(
|
48 |
+
'password' => array(),
|
49 |
+
) ),
|
50 |
+
),
|
51 |
+
array(
|
52 |
+
'methods' => WP_REST_Server::DELETABLE,
|
53 |
+
'callback' => array( $this, 'delete_item' ),
|
54 |
+
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
55 |
+
'args' => array(
|
56 |
+
'reassign' => array(),
|
57 |
+
),
|
58 |
+
),
|
59 |
+
) );
|
60 |
+
|
61 |
+
register_rest_route( 'wp/v2', '/users/me', array(
|
62 |
+
'methods' => WP_REST_Server::READABLE,
|
63 |
+
'callback' => array( $this, 'get_current_item' ),
|
64 |
+
'args' => array(
|
65 |
+
'context' => array(),
|
66 |
+
),
|
67 |
+
));
|
68 |
+
|
69 |
+
register_rest_route( 'wp/v2', '/users/schema', array(
|
70 |
+
'methods' => WP_REST_Server::READABLE,
|
71 |
+
'callback' => array( $this, 'get_public_item_schema' ),
|
72 |
+
) );
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Get all users
|
77 |
+
*
|
78 |
+
* @param WP_REST_Request $request Full details about the request.
|
79 |
+
* @return WP_Error|WP_REST_Response
|
80 |
+
*/
|
81 |
+
public function get_items( $request ) {
|
82 |
+
|
83 |
+
$prepared_args = array();
|
84 |
+
$prepared_args['order'] = $request['order'];
|
85 |
+
$prepared_args['number'] = $request['per_page'];
|
86 |
+
$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
|
87 |
+
$orderby_possibles = array(
|
88 |
+
'id' => 'ID',
|
89 |
+
'name' => 'display_name',
|
90 |
+
'registered_date' => 'registered',
|
91 |
+
);
|
92 |
+
$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
|
93 |
+
$prepared_args['search'] = $request['search'];
|
94 |
+
|
95 |
+
$prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
|
96 |
+
|
97 |
+
$query = new WP_User_Query( $prepared_args );
|
98 |
+
if ( is_wp_error( $query ) ) {
|
99 |
+
return $query;
|
100 |
+
}
|
101 |
+
|
102 |
+
$users = array();
|
103 |
+
foreach ( $query->results as $user ) {
|
104 |
+
$data = $this->prepare_item_for_response( $user, $request );
|
105 |
+
$users[] = $this->prepare_response_for_collection( $data );
|
106 |
+
}
|
107 |
+
|
108 |
+
$response = rest_ensure_response( $users );
|
109 |
+
unset( $prepared_args['number'] );
|
110 |
+
unset( $prepared_args['offset'] );
|
111 |
+
$count_query = new WP_User_Query( $prepared_args );
|
112 |
+
$total_users = $count_query->get_total();
|
113 |
+
$response->header( 'X-WP-Total', (int) $total_users );
|
114 |
+
$max_pages = ceil( $total_users / $request['per_page'] );
|
115 |
+
$response->header( 'X-WP-TotalPages', (int) $max_pages );
|
116 |
+
|
117 |
+
$base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/users' ) );
|
118 |
+
if ( $request['page'] > 1 ) {
|
119 |
+
$prev_page = $request['page'] - 1;
|
120 |
+
if ( $prev_page > $max_pages ) {
|
121 |
+
$prev_page = $max_pages;
|
122 |
+
}
|
123 |
+
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
124 |
+
$response->link_header( 'prev', $prev_link );
|
125 |
+
}
|
126 |
+
if ( $max_pages > $request['page'] ) {
|
127 |
+
$next_page = $request['page'] + 1;
|
128 |
+
$next_link = add_query_arg( 'page', $next_page, $base );
|
129 |
+
$response->link_header( 'next', $next_link );
|
130 |
+
}
|
131 |
+
|
132 |
+
return $response;
|
133 |
+
}
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Get a single user
|
137 |
+
*
|
138 |
+
* @param WP_REST_Request $request Full details about the request.
|
139 |
+
* @return WP_Error|WP_REST_Response
|
140 |
+
*/
|
141 |
+
public function get_item( $request ) {
|
142 |
+
$id = (int) $request['id'];
|
143 |
+
$user = get_userdata( $id );
|
144 |
+
|
145 |
+
if ( empty( $id ) || empty( $user->ID ) ) {
|
146 |
+
return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) );
|
147 |
+
}
|
148 |
+
|
149 |
+
$user = $this->prepare_item_for_response( $user, $request );
|
150 |
+
$response = rest_ensure_response( $user );
|
151 |
+
|
152 |
+
return $response;
|
153 |
+
}
|
154 |
+
|
155 |
+
/**
|
156 |
+
* Get the current user
|
157 |
+
*
|
158 |
+
* @param WP_REST_Request $request Full details about the request.
|
159 |
+
* @return WP_Error|WP_REST_Response
|
160 |
+
*/
|
161 |
+
public function get_current_item( $request ) {
|
162 |
+
$current_user_id = get_current_user_id();
|
163 |
+
if ( empty( $current_user_id ) ) {
|
164 |
+
return new WP_Error( 'rest_not_logged_in', __( 'You are not currently logged in.' ), array( 'status' => 401 ) );
|
165 |
+
}
|
166 |
+
|
167 |
+
$response = $this->get_item( array(
|
168 |
+
'id' => $current_user_id,
|
169 |
+
'context' => $request['context'],
|
170 |
+
));
|
171 |
+
if ( is_wp_error( $response ) ) {
|
172 |
+
return $response;
|
173 |
+
}
|
174 |
+
|
175 |
+
$response = rest_ensure_response( $response );
|
176 |
+
$response->header( 'Location', rest_url( sprintf( '/wp/v2/users/%d', $current_user_id ) ) );
|
177 |
+
$response->set_status( 302 );
|
178 |
+
|
179 |
+
return $response;
|
180 |
+
}
|
181 |
+
|
182 |
+
/**
|
183 |
+
* Create a single user
|
184 |
+
*
|
185 |
+
* @param WP_REST_Request $request Full details about the request.
|
186 |
+
* @return WP_Error|WP_REST_Response
|
187 |
+
*/
|
188 |
+
public function create_item( $request ) {
|
189 |
+
global $wp_roles;
|
190 |
+
|
191 |
+
if ( ! empty( $request['id'] ) ) {
|
192 |
+
return new WP_Error( 'rest_user_exists', __( 'Cannot create existing user.' ), array( 'status' => 400 ) );
|
193 |
+
}
|
194 |
+
|
195 |
+
if ( ! empty( $request['role'] ) && ! isset( $wp_roles->role_objects[ $request['role'] ] ) ) {
|
196 |
+
return new WP_Error( 'rest_user_invalid_role', __( 'Role is invalid.' ), array( 'status' => 400 ) );
|
197 |
+
}
|
198 |
+
|
199 |
+
$user = $this->prepare_item_for_database( $request );
|
200 |
+
|
201 |
+
if ( is_multisite() ) {
|
202 |
+
$ret = wpmu_validate_user_signup( $user->user_login, $user->user_email );
|
203 |
+
if ( is_wp_error( $ret['errors'] ) && ! empty( $ret['errors']->errors ) ) {
|
204 |
+
return $ret['errors'];
|
205 |
+
}
|
206 |
+
}
|
207 |
+
|
208 |
+
if ( is_multisite() ) {
|
209 |
+
$user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email );
|
210 |
+
if ( ! $user_id ) {
|
211 |
+
return new WP_Error( 'rest_user_create', __( 'Error creating new user.' ), array( 'status' => 500 ) );
|
212 |
+
}
|
213 |
+
$user->ID = $user_id;
|
214 |
+
$user_id = wp_update_user( $user );
|
215 |
+
if ( is_wp_error( $user_id ) ) {
|
216 |
+
return $user_id;
|
217 |
+
}
|
218 |
+
} else {
|
219 |
+
$user_id = wp_insert_user( $user );
|
220 |
+
if ( is_wp_error( $user_id ) ) {
|
221 |
+
return $user_id;
|
222 |
+
}
|
223 |
+
$user->ID = $user_id;
|
224 |
+
}
|
225 |
+
|
226 |
+
$this->update_additional_fields_for_object( $user, $request );
|
227 |
+
|
228 |
+
do_action( 'rest_insert_user', $user, $request, false );
|
229 |
+
|
230 |
+
$response = $this->get_item( array(
|
231 |
+
'id' => $user_id,
|
232 |
+
'context' => 'edit',
|
233 |
+
));
|
234 |
+
$response = rest_ensure_response( $response );
|
235 |
+
$response->set_status( 201 );
|
236 |
+
$response->header( 'Location', rest_url( '/wp/v2/users/' . $user_id ) );
|
237 |
+
|
238 |
+
return $response;
|
239 |
+
}
|
240 |
+
|
241 |
+
/**
|
242 |
+
* Update a single user
|
243 |
+
*
|
244 |
+
* @param WP_REST_Request $request Full details about the request.
|
245 |
+
* @return WP_Error|WP_REST_Response
|
246 |
+
*/
|
247 |
+
public function update_item( $request ) {
|
248 |
+
$id = (int) $request['id'];
|
249 |
+
|
250 |
+
$user = get_userdata( $id );
|
251 |
+
if ( ! $user ) {
|
252 |
+
return new WP_Error( 'rest_user_invalid_id', __( 'User ID is invalid.' ), array( 'status' => 400 ) );
|
253 |
+
}
|
254 |
+
|
255 |
+
if ( email_exists( $request['email'] ) && $request['email'] !== $user->user_email ) {
|
256 |
+
return new WP_Error( 'rest_user_invalid_email', __( 'Email address is invalid.' ), array( 'status' => 400 ) );
|
257 |
+
}
|
258 |
+
|
259 |
+
if ( ! empty( $request['username'] ) && $request['username'] !== $user->user_login ) {
|
260 |
+
return new WP_Error( 'rest_user_invalid_argument', __( "Username isn't editable" ), array( 'status' => 400 ) );
|
261 |
+
}
|
262 |
+
|
263 |
+
if ( ! empty( $request['slug'] ) && $request['slug'] !== $user->user_nicename && get_user_by( 'slug', $request['slug'] ) ) {
|
264 |
+
return new WP_Error( 'rest_user_invalid_slug', __( 'Slug is invalid.' ), array( 'status' => 400 ) );
|
265 |
+
}
|
266 |
+
|
267 |
+
if ( ! empty( $request['role'] ) ) {
|
268 |
+
$check_permission = $this->check_role_update( $id, $request['role'] );
|
269 |
+
if ( is_wp_error( $check_permission ) ) {
|
270 |
+
return $check_permission;
|
271 |
+
}
|
272 |
+
}
|
273 |
+
|
274 |
+
$user = $this->prepare_item_for_database( $request );
|
275 |
+
|
276 |
+
// Ensure we're operating on the same user we already checked
|
277 |
+
$user->ID = $id;
|
278 |
+
|
279 |
+
$user_id = wp_update_user( $user );
|
280 |
+
if ( is_wp_error( $user_id ) ) {
|
281 |
+
return $user_id;
|
282 |
+
}
|
283 |
+
|
284 |
+
$this->update_additional_fields_for_object( $user, $request );
|
285 |
+
|
286 |
+
do_action( 'rest_insert_user', $user, $request, false );
|
287 |
+
|
288 |
+
$response = $this->get_item( array(
|
289 |
+
'id' => $user_id,
|
290 |
+
'context' => 'edit',
|
291 |
+
));
|
292 |
+
$response = rest_ensure_response( $response );
|
293 |
+
$response->header( 'Location', rest_url( '/wp/v2/users/' . $user_id ) );
|
294 |
+
|
295 |
+
return $response;
|
296 |
+
}
|
297 |
+
|
298 |
+
/**
|
299 |
+
* Delete a single user
|
300 |
+
*
|
301 |
+
* @param WP_REST_Request $request Full details about the request.
|
302 |
+
* @return WP_Error|WP_REST_Response
|
303 |
+
*/
|
304 |
+
public function delete_item( $request ) {
|
305 |
+
$id = (int) $request['id'];
|
306 |
+
$reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
|
307 |
+
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
|
308 |
+
|
309 |
+
// We don't support trashing for this type, error out
|
310 |
+
if ( ! $force ) {
|
311 |
+
return new WP_Error( 'rest_trash_not_supported', __( 'Terms do not support trashing.' ), array( 'status' => 501 ) );
|
312 |
+
}
|
313 |
+
|
314 |
+
$user = get_userdata( $id );
|
315 |
+
if ( ! $user ) {
|
316 |
+
return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
|
317 |
+
}
|
318 |
+
|
319 |
+
if ( ! empty( $reassign ) ) {
|
320 |
+
if ( $reassign === $id || ! get_userdata( $reassign ) ) {
|
321 |
+
return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
|
322 |
+
}
|
323 |
+
}
|
324 |
+
|
325 |
+
$get_request = new WP_REST_Request( 'GET', rest_url( 'wp/v2/users/' . $id ) );
|
326 |
+
$get_request->set_param( 'context', 'edit' );
|
327 |
+
$orig_user = $this->prepare_item_for_response( $user, $get_request );
|
328 |
+
|
329 |
+
$result = wp_delete_user( $id, $reassign );
|
330 |
+
|
331 |
+
if ( ! $result ) {
|
332 |
+
return new WP_Error( 'rest_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 500 ) );
|
333 |
+
}
|
334 |
+
|
335 |
+
return $orig_user;
|
336 |
+
}
|
337 |
+
|
338 |
+
/**
|
339 |
+
* Check if a given request has access to list users
|
340 |
+
*
|
341 |
+
* @param WP_REST_Request $request Full details about the request.
|
342 |
+
* @return bool
|
343 |
+
*/
|
344 |
+
public function get_items_permissions_check( $request ) {
|
345 |
+
|
346 |
+
if ( ! current_user_can( 'list_users' ) ) {
|
347 |
+
return false;
|
348 |
+
}
|
349 |
+
|
350 |
+
return true;
|
351 |
+
}
|
352 |
+
|
353 |
+
/**
|
354 |
+
* Check if a given request has access to read a user
|
355 |
+
*
|
356 |
+
* @param WP_REST_Request $request Full details about the request.
|
357 |
+
* @return bool|WP_Error
|
358 |
+
*/
|
359 |
+
public function get_item_permissions_check( $request ) {
|
360 |
+
|
361 |
+
$id = (int) $request['id'];
|
362 |
+
$user = get_userdata( $id );
|
363 |
+
|
364 |
+
if ( empty( $id ) || empty( $user->ID ) ) {
|
365 |
+
return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) );
|
366 |
+
}
|
367 |
+
|
368 |
+
if ( get_current_user_id() === $id ) {
|
369 |
+
return true;
|
370 |
+
}
|
371 |
+
|
372 |
+
$context = ! empty( $request['context'] ) && in_array( $request['context'], array( 'edit', 'view', 'embed' ) ) ? $request['context'] : 'embed';
|
373 |
+
|
374 |
+
if ( 'edit' === $context && ! current_user_can( 'edit_user', $id ) ) {
|
375 |
+
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with edit context' ), array( 'status' => 403 ) );
|
376 |
+
} else if ( 'view' === $context && ! current_user_can( 'list_users' ) ) {
|
377 |
+
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with view context' ), array( 'status' => 403 ) );
|
378 |
+
} else if ( 'embed' === $context && ! count_user_posts( $id ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) {
|
379 |
+
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user' ), array( 'status' => 403 ) );
|
380 |
+
}
|
381 |
+
|
382 |
+
return true;
|
383 |
+
}
|
384 |
+
|
385 |
+
/**
|
386 |
+
* Check if a given request has access create users
|
387 |
+
*
|
388 |
+
* @param WP_REST_Request $request Full details about the request.
|
389 |
+
* @return bool
|
390 |
+
*/
|
391 |
+
public function create_item_permissions_check( $request ) {
|
392 |
+
|
393 |
+
if ( ! current_user_can( 'create_users' ) ) {
|
394 |
+
return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create users.' ), array( 'status' => 403 ) );
|
395 |
+
}
|
396 |
+
|
397 |
+
return true;
|
398 |
+
}
|
399 |
+
|
400 |
+
/**
|
401 |
+
* Check if a given request has access update a user
|
402 |
+
*
|
403 |
+
* @param WP_REST_Request $request Full details about the request.
|
404 |
+
* @return bool
|
405 |
+
*/
|
406 |
+
public function update_item_permissions_check( $request ) {
|
407 |
+
|
408 |
+
$id = (int) $request['id'];
|
409 |
+
|
410 |
+
if ( ! current_user_can( 'edit_user', $id ) ) {
|
411 |
+
return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit users.' ), array( 'status' => 403 ) );
|
412 |
+
}
|
413 |
+
|
414 |
+
if ( ! empty( $request['role'] ) && ! current_user_can( 'edit_users' ) ) {
|
415 |
+
return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of users.' ), array( 'status' => 403 ) );
|
416 |
+
}
|
417 |
+
|
418 |
+
return true;
|
419 |
+
}
|
420 |
+
|
421 |
+
/**
|
422 |
+
* Check if a given request has access delete a user
|
423 |
+
*
|
424 |
+
* @param WP_REST_Request $request Full details about the request.
|
425 |
+
* @return bool
|
426 |
+
*/
|
427 |
+
public function delete_item_permissions_check( $request ) {
|
428 |
+
|
429 |
+
$id = (int) $request['id'];
|
430 |
+
$reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
|
431 |
+
|
432 |
+
if ( ! current_user_can( 'delete_user', $id ) ) {
|
433 |
+
return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => 403 ) );
|
434 |
+
}
|
435 |
+
|
436 |
+
return true;
|
437 |
+
}
|
438 |
+
|
439 |
+
/**
|
440 |
+
* Prepare a single user output for response
|
441 |
+
*
|
442 |
+
* @param object $user User object.
|
443 |
+
* @param WP_REST_Request $request Request object.
|
444 |
+
* @return array $data Response data.
|
445 |
+
*/
|
446 |
+
public function prepare_item_for_response( $user, $request ) {
|
447 |
+
$data = array(
|
448 |
+
'avatar_urls' => rest_get_avatar_urls( $user->user_email ),
|
449 |
+
'capabilities' => $user->allcaps,
|
450 |
+
'description' => $user->description,
|
451 |
+
'email' => $user->user_email,
|
452 |
+
'extra_capabilities' => $user->caps,
|
453 |
+
'first_name' => $user->first_name,
|
454 |
+
'id' => $user->ID,
|
455 |
+
'last_name' => $user->last_name,
|
456 |
+
'link' => get_author_posts_url( $user->ID ),
|
457 |
+
'name' => $user->display_name,
|
458 |
+
'nickname' => $user->nickname,
|
459 |
+
'registered_date' => date( 'c', strtotime( $user->user_registered ) ),
|
460 |
+
'roles' => $user->roles,
|
461 |
+
'slug' => $user->user_nicename,
|
462 |
+
'url' => $user->user_url,
|
463 |
+
'username' => $user->user_login,
|
464 |
+
);
|
465 |
+
|
466 |
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
|
467 |
+
$data = $this->filter_response_by_context( $data, $context );
|
468 |
+
|
469 |
+
$data = $this->add_additional_fields_to_object( $data, $request );
|
470 |
+
|
471 |
+
// Wrap the data in a response object
|
472 |
+
$data = rest_ensure_response( $data );
|
473 |
+
|
474 |
+
$data->add_links( $this->prepare_links( $user ) );
|
475 |
+
|
476 |
+
return apply_filters( 'rest_prepare_user', $data, $user, $request );
|
477 |
+
}
|
478 |
+
|
479 |
+
/**
|
480 |
+
* Prepare links for the request.
|
481 |
+
*
|
482 |
+
* @param WP_Post $user User object.
|
483 |
+
* @return array Links for the given user.
|
484 |
+
*/
|
485 |
+
protected function prepare_links( $user ) {
|
486 |
+
$links = array(
|
487 |
+
'self' => array(
|
488 |
+
'href' => rest_url( sprintf( '/wp/v2/users/%d', $user->ID ) ),
|
489 |
+
),
|
490 |
+
'collection' => array(
|
491 |
+
'href' => rest_url( '/wp/v2/users' ),
|
492 |
+
),
|
493 |
+
);
|
494 |
+
|
495 |
+
return $links;
|
496 |
+
}
|
497 |
+
|
498 |
+
/**
|
499 |
+
* Prepare a single user for create or update
|
500 |
+
*
|
501 |
+
* @param WP_REST_Request $request Request object.
|
502 |
+
* @return object $prepared_user User object.
|
503 |
+
*/
|
504 |
+
protected function prepare_item_for_database( $request ) {
|
505 |
+
$prepared_user = new stdClass;
|
506 |
+
|
507 |
+
// required arguments.
|
508 |
+
if ( isset( $request['email'] ) ) {
|
509 |
+
$prepared_user->user_email = $request['email'];
|
510 |
+
}
|
511 |
+
if ( isset( $request['username'] ) ) {
|
512 |
+
$prepared_user->user_login = $request['username'];
|
513 |
+
}
|
514 |
+
if ( isset( $request['password'] ) ) {
|
515 |
+
$prepared_user->user_pass = $request['password'];
|
516 |
+
}
|
517 |
+
|
518 |
+
// optional arguments.
|
519 |
+
if ( isset( $request['id'] ) ) {
|
520 |
+
$prepared_user->ID = absint( $request['id'] );
|
521 |
+
}
|
522 |
+
if ( isset( $request['name'] ) ) {
|
523 |
+
$prepared_user->display_name = $request['name'];
|
524 |
+
}
|
525 |
+
if ( isset( $request['first_name'] ) ) {
|
526 |
+
$prepared_user->first_name = $request['first_name'];
|
527 |
+
}
|
528 |
+
if ( isset( $request['last_name'] ) ) {
|
529 |
+
$prepared_user->last_name = $request['last_name'];
|
530 |
+
}
|
531 |
+
if ( isset( $request['nickname'] ) ) {
|
532 |
+
$prepared_user->nickname = $request['nickname'];
|
533 |
+
}
|
534 |
+
if ( isset( $request['slug'] ) ) {
|
535 |
+
$prepared_user->user_nicename = $request['slug'];
|
536 |
+
}
|
537 |
+
if ( isset( $request['description'] ) ) {
|
538 |
+
$prepared_user->description = $request['description'];
|
539 |
+
}
|
540 |
+
if ( isset( $request['role'] ) ) {
|
541 |
+
$prepared_user->role = sanitize_text_field( $request['role'] );
|
542 |
+
}
|
543 |
+
if ( isset( $request['url'] ) ) {
|
544 |
+
$prepared_user->user_url = $request['url'];
|
545 |
+
}
|
546 |
+
|
547 |
+
return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
|
548 |
+
}
|
549 |
+
|
550 |
+
/**
|
551 |
+
* Determine if the current user is allowed to make the desired role change.
|
552 |
+
*
|
553 |
+
* @param integer $user_id
|
554 |
+
* @param string $role
|
555 |
+
* @return boolen|WP_Error
|
556 |
+
*/
|
557 |
+
protected function check_role_update( $user_id, $role ) {
|
558 |
+
global $wp_roles;
|
559 |
+
|
560 |
+
if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
|
561 |
+
return new WP_Error( 'rest_user_invalid_role', __( 'Role is invalid.' ), array( 'status' => 400 ) );
|
562 |
+
}
|
563 |
+
|
564 |
+
$potential_role = $wp_roles->role_objects[ $role ];
|
565 |
+
|
566 |
+
// Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
|
567 |
+
// Multisite super admins can freely edit their blog roles -- they possess all caps.
|
568 |
+
if ( ( is_multisite() && current_user_can( 'manage_sites' ) ) || get_current_user_id() !== $user_id || $potential_role->has_cap( 'edit_users' ) ) {
|
569 |
+
// The new role must be editable by the logged-in user.
|
570 |
+
$editable_roles = get_editable_roles();
|
571 |
+
if ( empty( $editable_roles[ $role ] ) ) {
|
572 |
+
return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give users that role.' ), array( 'status' => 403 ) );
|
573 |
+
}
|
574 |
+
|
575 |
+
return true;
|
576 |
+
}
|
577 |
+
|
578 |
+
return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give users that role.' ), array( 'status' => 403 ) );
|
579 |
+
}
|
580 |
+
|
581 |
+
/**
|
582 |
+
* Get the User's schema, conforming to JSON Schema
|
583 |
+
*
|
584 |
+
* @return array
|
585 |
+
*/
|
586 |
+
public function get_item_schema() {
|
587 |
+
$avatar_properties = array();
|
588 |
+
|
589 |
+
$avatar_sizes = rest_get_avatar_sizes();
|
590 |
+
foreach ( $avatar_sizes as $size ) {
|
591 |
+
$avatar_properties[ $size ] = array(
|
592 |
+
'description' => 'Avatar URL with image size of ' . $size . ' pixels.',
|
593 |
+
'type' => 'uri',
|
594 |
+
'context' => array( 'embed', 'view', 'edit' ),
|
595 |
+
);
|
596 |
+
}
|
597 |
+
|
598 |
+
$schema = array(
|
599 |
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
600 |
+
'title' => 'user',
|
601 |
+
'type' => 'object',
|
602 |
+
'properties' => array(
|
603 |
+
'avatar_urls' => array(
|
604 |
+
'description' => 'Avatar URLs for the object.',
|
605 |
+
'type' => 'object',
|
606 |
+
'context' => array( 'embed', 'view', 'edit' ),
|
607 |
+
'readonly' => true,
|
608 |
+
'properties' => $avatar_properties,
|
609 |
+
),
|
610 |
+
'capabilities' => array(
|
611 |
+
'description' => 'All capabilities assigned to the user.',
|
612 |
+
'type' => 'object',
|
613 |
+
'context' => array( 'view', 'edit' ),
|
614 |
+
),
|
615 |
+
'description' => array(
|
616 |
+
'description' => 'Description of the object.',
|
617 |
+
'type' => 'string',
|
618 |
+
'context' => array( 'embed', 'view', 'edit' ),
|
619 |
+
'arg_options' => array(
|
620 |
+
'sanitize_callback' => 'wp_filter_post_kses',
|
621 |
+
),
|
622 |
+
),
|
623 |
+
'email' => array(
|
624 |
+
'description' => 'The email address for the object.',
|
625 |
+
'type' => 'string',
|
626 |
+
'format' => 'email',
|
627 |
+
'context' => array( 'view', 'edit' ),
|
628 |
+
'required' => true,
|
629 |
+
),
|
630 |
+
'extra_capabilities' => array(
|
631 |
+
'description' => 'Any extra capabilities assigned to the user.',
|
632 |
+
'type' => 'object',
|
633 |
+
'context' => array( 'edit' ),
|
634 |
+
'readonly' => true,
|
635 |
+
),
|
636 |
+
'first_name' => array(
|
637 |
+
'description' => 'First name for the object.',
|
638 |
+
'type' => 'string',
|
639 |
+
'context' => array( 'view', 'edit' ),
|
640 |
+
'arg_options' => array(
|
641 |
+
'sanitize_callback' => 'sanitize_text_field',
|
642 |
+
),
|
643 |
+
),
|
644 |
+
'id' => array(
|
645 |
+
'description' => 'Unique identifier for the object.',
|
646 |
+
'type' => 'integer',
|
647 |
+
'context' => array( 'embed', 'view', 'edit' ),
|
648 |
+
'readonly' => true,
|
649 |
+
),
|
650 |
+
'last_name' => array(
|
651 |
+
'description' => 'Last name for the object.',
|
652 |
+
'type' => 'string',
|
653 |
+
'context' => array( 'view', 'edit' ),
|
654 |
+
'arg_options' => array(
|
655 |
+
'sanitize_callback' => 'sanitize_text_field',
|
656 |
+
),
|
657 |
+
),
|
658 |
+
'link' => array(
|
659 |
+
'description' => 'Author URL to the object.',
|
660 |
+
'type' => 'string',
|
661 |
+
'format' => 'uri',
|
662 |
+
'context' => array( 'embed', 'view', 'edit' ),
|
663 |
+
'readonly' => true,
|
664 |
+
),
|
665 |
+
'name' => array(
|
666 |
+
'description' => 'Display name for the object.',
|
667 |
+
'type' => 'string',
|
668 |
+
'context' => array( 'embed', 'view', 'edit' ),
|
669 |
+
'arg_options' => array(
|
670 |
+
'sanitize_callback' => 'sanitize_text_field',
|
671 |
+
),
|
672 |
+
),
|
673 |
+
'nickname' => array(
|
674 |
+
'description' => 'The nickname for the object.',
|
675 |
+
'type' => 'string',
|
676 |
+
'context' => array( 'view', 'edit' ),
|
677 |
+
'arg_options' => array(
|
678 |
+
'sanitize_callback' => 'sanitize_text_field',
|
679 |
+
),
|
680 |
+
),
|
681 |
+
'registered_date' => array(
|
682 |
+
'description' => 'Registration date for the user.',
|
683 |
+
'type' => 'date-time',
|
684 |
+
'context' => array( 'view', 'edit' ),
|
685 |
+
'readonly' => true,
|
686 |
+
),
|
687 |
+
'roles' => array(
|
688 |
+
'description' => 'Roles assigned to the user.',
|
689 |
+
'type' => 'array',
|
690 |
+
'context' => array( 'view', 'edit' ),
|
691 |
+
),
|
692 |
+
'slug' => array(
|
693 |
+
'description' => 'An alphanumeric identifier for the object unique to its type.',
|
694 |
+
'type' => 'string',
|
695 |
+
'context' => array( 'view', 'edit' ),
|
696 |
+
'arg_options' => array(
|
697 |
+
'sanitize_callback' => 'sanitize_title',
|
698 |
+
),
|
699 |
+
),
|
700 |
+
'url' => array(
|
701 |
+
'description' => 'URL of the object.',
|
702 |
+
'type' => 'string',
|
703 |
+
'format' => 'uri',
|
704 |
+
'context' => array( 'embed', 'view', 'edit' ),
|
705 |
+
'readonly' => true,
|
706 |
+
),
|
707 |
+
'username' => array(
|
708 |
+
'description' => 'Login name for the user.',
|
709 |
+
'type' => 'string',
|
710 |
+
'context' => array( 'edit' ),
|
711 |
+
'required' => true,
|
712 |
+
'arg_options' => array(
|
713 |
+
'sanitize_callback' => 'sanitize_user',
|
714 |
+
),
|
715 |
+
),
|
716 |
+
),
|
717 |
+
);
|
718 |
+
return $this->add_additional_fields_schema( $schema );
|
719 |
+
}
|
720 |
+
|
721 |
+
/**
|
722 |
+
* Get the query params for collections
|
723 |
+
*
|
724 |
+
* @return array
|
725 |
+
*/
|
726 |
+
public function get_collection_params() {
|
727 |
+
$query_params = parent::get_collection_params();
|
728 |
+
$query_params['context'] = array(
|
729 |
+
'default' => 'view',
|
730 |
+
'description' => 'Change the response format based on request context.',
|
731 |
+
'enum' => array( 'view', 'edit' ),
|
732 |
+
'sanitize_callback' => 'sanitize_key',
|
733 |
+
'type' => 'string',
|
734 |
+
);
|
735 |
+
$query_params['order'] = array(
|
736 |
+
'default' => 'asc',
|
737 |
+
'description' => 'Order sort attribute ascending or descending.',
|
738 |
+
'enum' => array( 'asc', 'desc' ),
|
739 |
+
'sanitize_callback' => 'sanitize_key',
|
740 |
+
'type' => 'string',
|
741 |
+
);
|
742 |
+
$query_params['orderby'] = array(
|
743 |
+
'default' => 'name',
|
744 |
+
'description' => 'Sort collection by object attribute.',
|
745 |
+
'enum' => array( 'id', 'name', 'registered_date' ),
|
746 |
+
'sanitize_callback' => 'sanitize_key',
|
747 |
+
'type' => 'string',
|
748 |
+
);
|
749 |
+
return $query_params;
|
750 |
+
}
|
751 |
+
}
|
lib/infrastructure/class-jsonserializable.php
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Compatibility shim for PHP <5.4
|
4 |
+
*
|
5 |
+
* @link http://php.net/jsonserializable
|
6 |
+
*
|
7 |
+
* @package WordPress
|
8 |
+
* @subpackage JSON API
|
9 |
+
*/
|
10 |
+
|
11 |
+
if ( ! interface_exists( 'JsonSerializable' ) ) {
|
12 |
+
define( 'WP_JSON_SERIALIZE_COMPATIBLE', true );
|
13 |
+
// @codingStandardsIgnoreStart
|
14 |
+
interface JsonSerializable {
|
15 |
+
public function jsonSerialize();
|
16 |
+
}
|
17 |
+
// @codingStandardsIgnoreEnd
|
18 |
+
}
|
lib/infrastructure/class-wp-http-response.php
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_HTTP_Response implements WP_HTTP_ResponseInterface {
|
4 |
+
/**
|
5 |
+
* @var mixed
|
6 |
+
*/
|
7 |
+
public $data;
|
8 |
+
/**
|
9 |
+
* @var integer
|
10 |
+
*/
|
11 |
+
public $headers;
|
12 |
+
/**
|
13 |
+
* @var array
|
14 |
+
*/
|
15 |
+
public $status;
|
16 |
+
/**
|
17 |
+
* Constructor
|
18 |
+
*
|
19 |
+
* @param mixed $data Response data
|
20 |
+
* @param integer $status HTTP status code
|
21 |
+
* @param array $headers HTTP header map
|
22 |
+
*/
|
23 |
+
public function __construct( $data = null, $status = 200, $headers = array() ) {
|
24 |
+
$this->data = $data;
|
25 |
+
$this->set_status( $status );
|
26 |
+
$this->set_headers( $headers );
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Get headers associated with the response
|
31 |
+
*
|
32 |
+
* @return array Map of header name to header value
|
33 |
+
*/
|
34 |
+
public function get_headers() {
|
35 |
+
return $this->headers;
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Set all header values
|
40 |
+
*
|
41 |
+
* @param array $headers Map of header name to header value
|
42 |
+
*/
|
43 |
+
public function set_headers( $headers ) {
|
44 |
+
$this->headers = $headers;
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Set a single HTTP header
|
49 |
+
*
|
50 |
+
* @param string $key Header name
|
51 |
+
* @param string $value Header value
|
52 |
+
* @param boolean $replace Replace an existing header of the same name?
|
53 |
+
*/
|
54 |
+
public function header( $key, $value, $replace = true ) {
|
55 |
+
if ( $replace || ! isset( $this->headers[ $key ] ) ) {
|
56 |
+
$this->headers[ $key ] = $value;
|
57 |
+
} else {
|
58 |
+
$this->headers[ $key ] .= ', ' . $value;
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Get the HTTP return code for the response
|
64 |
+
*
|
65 |
+
* @return integer 3-digit HTTP status code
|
66 |
+
*/
|
67 |
+
public function get_status() {
|
68 |
+
return $this->status;
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Set the HTTP status code
|
73 |
+
*
|
74 |
+
* @param int $code HTTP status
|
75 |
+
*/
|
76 |
+
public function set_status( $code ) {
|
77 |
+
$this->status = absint( $code );
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Get the response data
|
82 |
+
*
|
83 |
+
* @return mixed
|
84 |
+
*/
|
85 |
+
public function get_data() {
|
86 |
+
return $this->data;
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Set the response data
|
91 |
+
*
|
92 |
+
* @param mixed $data
|
93 |
+
*/
|
94 |
+
public function set_data( $data ) {
|
95 |
+
$this->data = $data;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Get the response data for JSON serialization
|
100 |
+
*
|
101 |
+
* It is expected that in most implementations, this will return the same as
|
102 |
+
* {@see get_data()}, however this may be different if you want to do custom
|
103 |
+
* JSON data handling.
|
104 |
+
*
|
105 |
+
* @return mixed Any JSON-serializable value
|
106 |
+
*/
|
107 |
+
// @codingStandardsIgnoreStart
|
108 |
+
public function jsonSerialize() {
|
109 |
+
// @codingStandardsIgnoreEnd
|
110 |
+
return $this->get_data();
|
111 |
+
}
|
112 |
+
}
|
lib/infrastructure/class-wp-http-responseinterface.php
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
interface WP_HTTP_ResponseInterface extends JsonSerializable {
|
4 |
+
/**
|
5 |
+
* Get headers associated with the response
|
6 |
+
*
|
7 |
+
* @return array Map of header name to header value
|
8 |
+
*/
|
9 |
+
public function get_headers();
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Get the HTTP return code for the response
|
13 |
+
*
|
14 |
+
* @return integer 3-digit HTTP status code
|
15 |
+
*/
|
16 |
+
public function get_status();
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Get the response data
|
20 |
+
*
|
21 |
+
* @return mixed
|
22 |
+
*/
|
23 |
+
public function get_data();
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Get the response data for JSON serialization
|
27 |
+
*
|
28 |
+
* It is expected that in most implementations, this will return the same as
|
29 |
+
* {@see get_data()}, however this may be different if you want to do custom
|
30 |
+
* JSON data handling.
|
31 |
+
*
|
32 |
+
* @return mixed Any JSON-serializable value
|
33 |
+
*/
|
34 |
+
// public function jsonSerialize();
|
35 |
+
}
|
lib/infrastructure/class-wp-rest-request.php
ADDED
@@ -0,0 +1,764 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Request object
|
5 |
+
*
|
6 |
+
* Contains data from the request, to be passed to the callback.
|
7 |
+
*
|
8 |
+
* Note: This implements ArrayAccess, and acts as an array of parameters when
|
9 |
+
* used in that manner. It does not use ArrayObject (as we cannot rely on SPL),
|
10 |
+
* so be aware it may have non-array behaviour in some cases.
|
11 |
+
*
|
12 |
+
* @package WordPress
|
13 |
+
*/
|
14 |
+
class WP_REST_Request implements ArrayAccess {
|
15 |
+
/**
|
16 |
+
* HTTP method
|
17 |
+
*
|
18 |
+
* @var string
|
19 |
+
*/
|
20 |
+
protected $method = '';
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Parameters passed to the request
|
24 |
+
*
|
25 |
+
* These typically come from the `$_GET`, `$_POST` and `$_FILES`
|
26 |
+
* superglobals when being created from the global scope.
|
27 |
+
*
|
28 |
+
* @var array Contains GET, POST and FILES keys mapping to arrays of data
|
29 |
+
*/
|
30 |
+
protected $params;
|
31 |
+
|
32 |
+
/**
|
33 |
+
* HTTP headers for the request
|
34 |
+
*
|
35 |
+
* @var array Map of key to value. Key is always lowercase, as per HTTP specification
|
36 |
+
*/
|
37 |
+
protected $headers = array();
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Body data
|
41 |
+
*
|
42 |
+
* @var string Binary data from the request
|
43 |
+
*/
|
44 |
+
protected $body = null;
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Route matched for the request
|
48 |
+
*
|
49 |
+
* @var string
|
50 |
+
*/
|
51 |
+
protected $route;
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Attributes (options) for the route that was matched
|
55 |
+
*
|
56 |
+
* This is the options array used when the route was registered, typically
|
57 |
+
* containing the callback as well as the valid methods for the route.
|
58 |
+
*
|
59 |
+
* @return array Attributes for the request
|
60 |
+
*/
|
61 |
+
protected $attributes = array();
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Have we parsed the JSON data yet?
|
65 |
+
*
|
66 |
+
* Allows lazy-parsing of JSON data where possible.
|
67 |
+
*
|
68 |
+
* @var boolean
|
69 |
+
*/
|
70 |
+
protected $parsed_json = false;
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Have we parsed body data yet?
|
74 |
+
*
|
75 |
+
* @var boolean
|
76 |
+
*/
|
77 |
+
protected $parsed_body = false;
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Constructor
|
81 |
+
*/
|
82 |
+
public function __construct( $method = '', $route = '', $attributes = array() ) {
|
83 |
+
$this->params = array(
|
84 |
+
'URL' => array(),
|
85 |
+
'GET' => array(),
|
86 |
+
'POST' => array(),
|
87 |
+
'FILES' => array(),
|
88 |
+
|
89 |
+
// See parse_json_params
|
90 |
+
'JSON' => null,
|
91 |
+
|
92 |
+
'defaults' => array(),
|
93 |
+
);
|
94 |
+
|
95 |
+
$this->set_method( $method );
|
96 |
+
$this->set_route( $route );
|
97 |
+
$this->set_attributes( $attributes );
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Get HTTP method for the request
|
102 |
+
*
|
103 |
+
* @return string HTTP method
|
104 |
+
*/
|
105 |
+
public function get_method() {
|
106 |
+
return $this->method;
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Set HTTP method for the request
|
111 |
+
*
|
112 |
+
* @param string $method HTTP method
|
113 |
+
*/
|
114 |
+
public function set_method( $method ) {
|
115 |
+
$this->method = strtoupper( $method );
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Get all headers from the request
|
120 |
+
*
|
121 |
+
* @return array Map of key to value. Key is always lowercase, as per HTTP specification
|
122 |
+
*/
|
123 |
+
public function get_headers() {
|
124 |
+
return $this->headers;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Canonicalize header name
|
129 |
+
*
|
130 |
+
* Ensures that header names are always treated the same regardless of
|
131 |
+
* source. Header names are always case insensitive.
|
132 |
+
*
|
133 |
+
* Note that we treat `-` (dashes) and `_` (underscores) as the same
|
134 |
+
* character, as per header parsing rules in both Apache and nginx.
|
135 |
+
*
|
136 |
+
* @link http://stackoverflow.com/q/18185366
|
137 |
+
* @link http://wiki.nginx.org/Pitfalls#Missing_.28disappearing.29_HTTP_headers
|
138 |
+
* @link http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
|
139 |
+
*
|
140 |
+
* @param string $key Header name
|
141 |
+
* @return string Canonicalized name
|
142 |
+
*/
|
143 |
+
public static function canonicalize_header_name( $key ) {
|
144 |
+
$key = strtolower( $key );
|
145 |
+
$key = str_replace( '-', '_', $key );
|
146 |
+
|
147 |
+
return $key;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Get header from request
|
152 |
+
*
|
153 |
+
* If the header has multiple values, they will be concatenated with a comma
|
154 |
+
* as per the HTTP specification. Be aware that some non-compliant headers
|
155 |
+
* (notably cookie headers) cannot be joined this way.
|
156 |
+
*
|
157 |
+
* @param string $key Header name, will be canonicalized to lowercase
|
158 |
+
* @return string|null String value if set, null otherwise
|
159 |
+
*/
|
160 |
+
public function get_header( $key ) {
|
161 |
+
$key = $this->canonicalize_header_name( $key );
|
162 |
+
|
163 |
+
if ( ! isset( $this->headers[ $key ] ) ) {
|
164 |
+
return null;
|
165 |
+
}
|
166 |
+
|
167 |
+
return implode( ',', $this->headers[ $key ] );
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* Get header values from request
|
172 |
+
*
|
173 |
+
* @param string $key Header name, will be canonicalized to lowercase
|
174 |
+
* @return array|null List of string values if set, null otherwise
|
175 |
+
*/
|
176 |
+
public function get_header_as_array( $key ) {
|
177 |
+
$key = $this->canonicalize_header_name( $key );
|
178 |
+
|
179 |
+
if ( ! isset( $this->headers[ $key ] ) ) {
|
180 |
+
return null;
|
181 |
+
}
|
182 |
+
|
183 |
+
return $this->headers[ $key ];
|
184 |
+
}
|
185 |
+
|
186 |
+
/**
|
187 |
+
* Set header on request
|
188 |
+
*
|
189 |
+
* @param string $key Header name
|
190 |
+
* @param string|string[] $value Header value, or list of values
|
191 |
+
*/
|
192 |
+
public function set_header( $key, $value ) {
|
193 |
+
$key = $this->canonicalize_header_name( $key );
|
194 |
+
$value = (array) $value;
|
195 |
+
|
196 |
+
$this->headers[ $key ] = $value;
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Append a header value for the given header
|
201 |
+
*
|
202 |
+
* @param string $key Header name
|
203 |
+
* @param string|string[] $value Header value, or list of values
|
204 |
+
*/
|
205 |
+
public function add_header( $key, $value ) {
|
206 |
+
$key = $this->canonicalize_header_name( $key );
|
207 |
+
$value = (array) $value;
|
208 |
+
|
209 |
+
if ( ! isset( $this->headers[ $key ] ) ) {
|
210 |
+
$this->headers[ $key ] = array();
|
211 |
+
}
|
212 |
+
|
213 |
+
$this->headers[ $key ] = array_merge( $this->headers[ $key ], $value );
|
214 |
+
}
|
215 |
+
|
216 |
+
/**
|
217 |
+
* Remove all values for a header
|
218 |
+
*
|
219 |
+
* @param string $key Header name
|
220 |
+
*/
|
221 |
+
public function remove_header( $key ) {
|
222 |
+
unset( $this->headers[ $key ] );
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Set headers on the request
|
227 |
+
*
|
228 |
+
* @param array $headers Map of header name to value
|
229 |
+
* @param boolean $override If true, replace the request's headers. Otherwise, merge with existing.
|
230 |
+
*/
|
231 |
+
public function set_headers( $headers, $override = true ) {
|
232 |
+
if ( true === $override ) {
|
233 |
+
$this->headers = array();
|
234 |
+
}
|
235 |
+
|
236 |
+
foreach ( $headers as $key => $value ) {
|
237 |
+
$this->set_header( $key, $value );
|
238 |
+
}
|
239 |
+
}
|
240 |
+
|
241 |
+
/**
|
242 |
+
* Get the content-type of the request
|
243 |
+
*
|
244 |
+
* @return array Map containing 'value' and 'parameters' keys
|
245 |
+
*/
|
246 |
+
public function get_content_type() {
|
247 |
+
$value = $this->get_header( 'content-type' );
|
248 |
+
if ( empty( $value ) ) {
|
249 |
+
return null;
|
250 |
+
}
|
251 |
+
|
252 |
+
$parameters = '';
|
253 |
+
if ( strpos( $value, ';' ) ) {
|
254 |
+
list( $value, $parameters ) = explode( ';', $value, 2 );
|
255 |
+
}
|
256 |
+
|
257 |
+
$value = strtolower( $value );
|
258 |
+
if ( strpos( $value, '/' ) === false ) {
|
259 |
+
return null;
|
260 |
+
}
|
261 |
+
|
262 |
+
// Parse type and subtype out
|
263 |
+
list( $type, $subtype ) = explode( '/', $value, 2 );
|
264 |
+
|
265 |
+
$data = compact( 'value', 'type', 'subtype', 'parameters' );
|
266 |
+
$data = array_map( 'trim', $data );
|
267 |
+
|
268 |
+
return $data;
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* Get the parameter priority order
|
273 |
+
*
|
274 |
+
* Used when checking parameters in {@see get_param}.
|
275 |
+
*
|
276 |
+
* @return string[] List of types to check, in order of priority
|
277 |
+
*/
|
278 |
+
protected function get_parameter_order() {
|
279 |
+
$order = array();
|
280 |
+
$order[] = 'JSON';
|
281 |
+
|
282 |
+
$this->parse_json_params();
|
283 |
+
|
284 |
+
// Ensure we parse the body data
|
285 |
+
$body = $this->get_body();
|
286 |
+
if ( $this->method !== 'POST' && ! empty( $body ) ) {
|
287 |
+
$this->parse_body_params();
|
288 |
+
}
|
289 |
+
|
290 |
+
$accepts_body_data = array( 'POST', 'PUT', 'PATCH' );
|
291 |
+
if ( in_array( $this->method, $accepts_body_data ) ) {
|
292 |
+
$order[] = 'POST';
|
293 |
+
}
|
294 |
+
|
295 |
+
$order[] = 'GET';
|
296 |
+
$order[] = 'URL';
|
297 |
+
$order[] = 'defaults';
|
298 |
+
|
299 |
+
/**
|
300 |
+
* Alter the parameter checking order
|
301 |
+
*
|
302 |
+
* The order affects which parameters are checked when using
|
303 |
+
* {@see get_param} and family. This acts similarly to PHP's
|
304 |
+
* `request_order` setting.
|
305 |
+
*
|
306 |
+
* @param string[] $order List of types to check, in order of priority
|
307 |
+
* @param WP_REST_Request $this Request object
|
308 |
+
*/
|
309 |
+
return apply_filters( 'rest_request_parameter_order', $order, $this );
|
310 |
+
}
|
311 |
+
|
312 |
+
/**
|
313 |
+
* Get a parameter from the request
|
314 |
+
*
|
315 |
+
* @param string $key Parameter name
|
316 |
+
* @return mixed|null Value if set, null otherwise
|
317 |
+
*/
|
318 |
+
public function get_param( $key ) {
|
319 |
+
$order = $this->get_parameter_order();
|
320 |
+
|
321 |
+
foreach ( $order as $type ) {
|
322 |
+
// Do we have the parameter for this type?
|
323 |
+
if ( isset( $this->params[ $type ][ $key ] ) ) {
|
324 |
+
return $this->params[ $type ][ $key ];
|
325 |
+
}
|
326 |
+
}
|
327 |
+
|
328 |
+
return null;
|
329 |
+
}
|
330 |
+
|
331 |
+
/**
|
332 |
+
* Set a parameter on the request
|
333 |
+
*
|
334 |
+
* @param string $key Parameter name
|
335 |
+
* @param mixed $value Parameter value
|
336 |
+
*/
|
337 |
+
public function set_param( $key, $value ) {
|
338 |
+
switch ( $this->method ) {
|
339 |
+
case 'POST':
|
340 |
+
$this->params['POST'][ $key ] = $value;
|
341 |
+
break;
|
342 |
+
|
343 |
+
default:
|
344 |
+
$this->params['GET'][ $key ] = $value;
|
345 |
+
break;
|
346 |
+
}
|
347 |
+
}
|
348 |
+
|
349 |
+
/**
|
350 |
+
* Get merged parameters from the request
|
351 |
+
*
|
352 |
+
* The equivalent of {@see get_param}, but returns all parameters for the
|
353 |
+
* request. Handles merging all the available values into a single array.
|
354 |
+
*
|
355 |
+
* @return array Map of key to value
|
356 |
+
*/
|
357 |
+
public function get_params() {
|
358 |
+
$order = $this->get_parameter_order();
|
359 |
+
$order = array_reverse( $order, true );
|
360 |
+
|
361 |
+
$params = array();
|
362 |
+
foreach ( $order as $type ) {
|
363 |
+
$params = array_merge( $params, (array) $this->params[ $type ] );
|
364 |
+
}
|
365 |
+
|
366 |
+
return $params;
|
367 |
+
}
|
368 |
+
|
369 |
+
/**
|
370 |
+
* Get parameters from the route itself
|
371 |
+
*
|
372 |
+
* These are parsed from the URL using the regex.
|
373 |
+
*
|
374 |
+
* @return array Parameter map of key to value
|
375 |
+
*/
|
376 |
+
public function get_url_params() {
|
377 |
+
return $this->params['URL'];
|
378 |
+
}
|
379 |
+
|
380 |
+
/**
|
381 |
+
* Set parameters from the route
|
382 |
+
*
|
383 |
+
* Typically, this is set after parsing the URL.
|
384 |
+
*
|
385 |
+
* @param array $params Parameter map of key to value
|
386 |
+
*/
|
387 |
+
public function set_url_params( $params ) {
|
388 |
+
$this->params['URL'] = $params;
|
389 |
+
}
|
390 |
+
|
391 |
+
/**
|
392 |
+
* Get parameters from the query string
|
393 |
+
*
|
394 |
+
* These are the parameters you'd typically find in `$_GET`
|
395 |
+
*
|
396 |
+
* @return array Parameter map of key to value
|
397 |
+
*/
|
398 |
+
public function get_query_params() {
|
399 |
+
return $this->params['GET'];
|
400 |
+
}
|
401 |
+
|
402 |
+
/**
|
403 |
+
* Set parameters from the query string
|
404 |
+
*
|
405 |
+
* Typically, this is set from `$_GET`
|
406 |
+
*
|
407 |
+
* @param array $params Parameter map of key to value
|
408 |
+
*/
|
409 |
+
public function set_query_params( $params ) {
|
410 |
+
$this->params['GET'] = $params;
|
411 |
+
}
|
412 |
+
|
413 |
+
/**
|
414 |
+
* Get parameters from the body
|
415 |
+
*
|
416 |
+
* These are the parameters you'd typically find in `$_POST`
|
417 |
+
*
|
418 |
+
* @return array Parameter map of key to value
|
419 |
+
*/
|
420 |
+
public function get_body_params() {
|
421 |
+
return $this->params['POST'];
|
422 |
+
}
|
423 |
+
|
424 |
+
/**
|
425 |
+
* Set parameters from the body
|
426 |
+
*
|
427 |
+
* Typically, this is set from `$_POST`
|
428 |
+
*
|
429 |
+
* @param array $params Parameter map of key to value
|
430 |
+
*/
|
431 |
+
public function set_body_params( $params ) {
|
432 |
+
$this->params['POST'] = $params;
|
433 |
+
}
|
434 |
+
|
435 |
+
/**
|
436 |
+
* Get multipart file parameters from the body
|
437 |
+
*
|
438 |
+
* These are the parameters you'd typically find in `$_FILES`
|
439 |
+
*
|
440 |
+
* @return array Parameter map of key to value
|
441 |
+
*/
|
442 |
+
public function get_file_params() {
|
443 |
+
return $this->params['FILES'];
|
444 |
+
}
|
445 |
+
|
446 |
+
/**
|
447 |
+
* Set multipart file parameters from the body
|
448 |
+
*
|
449 |
+
* Typically, this is set from `$_FILES`
|
450 |
+
*
|
451 |
+
* @param array $params Parameter map of key to value
|
452 |
+
*/
|
453 |
+
public function set_file_params( $params ) {
|
454 |
+
$this->params['FILES'] = $params;
|
455 |
+
}
|
456 |
+
|
457 |
+
/**
|
458 |
+
* Get default parameters
|
459 |
+
*
|
460 |
+
* These are the parameters set in the route registration
|
461 |
+
*
|
462 |
+
* @return array Parameter map of key to value
|
463 |
+
*/
|
464 |
+
public function get_default_params() {
|
465 |
+
return $this->params['defaults'];
|
466 |
+
}
|
467 |
+
|
468 |
+
/**
|
469 |
+
* Set default parameters
|
470 |
+
*
|
471 |
+
* These are the parameters set in the route registration
|
472 |
+
*
|
473 |
+
* @param array $params Parameter map of key to value
|
474 |
+
*/
|
475 |
+
public function set_default_params( $params ) {
|
476 |
+
$this->params['defaults'] = $params;
|
477 |
+
}
|
478 |
+
|
479 |
+
/**
|
480 |
+
* Get body content
|
481 |
+
*
|
482 |
+
* @return string Binary data from the request body
|
483 |
+
*/
|
484 |
+
public function get_body() {
|
485 |
+
return $this->body;
|
486 |
+
}
|
487 |
+
|
488 |
+
/**
|
489 |
+
* Set body content
|
490 |
+
*
|
491 |
+
* @param string $data Binary data from the request body
|
492 |
+
*/
|
493 |
+
public function set_body( $data ) {
|
494 |
+
$this->body = $data;
|
495 |
+
|
496 |
+
// Enable lazy parsing
|
497 |
+
$this->parsed_json = false;
|
498 |
+
$this->parsed_body = false;
|
499 |
+
$this->params['JSON'] = null;
|
500 |
+
}
|
501 |
+
|
502 |
+
/**
|
503 |
+
* Get parameters from a JSON-formatted body
|
504 |
+
*
|
505 |
+
* @return array Parameter map of key to value
|
506 |
+
*/
|
507 |
+
public function get_json_params() {
|
508 |
+
// Ensure the parameters have been parsed out
|
509 |
+
$this->parse_json_params();
|
510 |
+
|
511 |
+
return $this->params['JSON'];
|
512 |
+
}
|
513 |
+
|
514 |
+
/**
|
515 |
+
* Parse the JSON parameters
|
516 |
+
*
|
517 |
+
* Avoids parsing the JSON data until we need to access it.
|
518 |
+
*/
|
519 |
+
protected function parse_json_params() {
|
520 |
+
if ( $this->parsed_json ) {
|
521 |
+
return;
|
522 |
+
}
|
523 |
+
$this->parsed_json = true;
|
524 |
+
|
525 |
+
// Check that we actually got JSON
|
526 |
+
$content_type = $this->get_content_type();
|
527 |
+
if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
|
528 |
+
return;
|
529 |
+
}
|
530 |
+
|
531 |
+
$params = json_decode( $this->get_body(), true );
|
532 |
+
|
533 |
+
// Check for a parsing error
|
534 |
+
//
|
535 |
+
// Note that due to WP's JSON compatibility functions, json_last_error
|
536 |
+
// might not be defined: https://core.trac.wordpress.org/ticket/27799
|
537 |
+
if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) {
|
538 |
+
return;
|
539 |
+
}
|
540 |
+
|
541 |
+
$this->params['JSON'] = $params;
|
542 |
+
}
|
543 |
+
|
544 |
+
/**
|
545 |
+
* Parse body parameters.
|
546 |
+
*
|
547 |
+
* Parses out URL-encoded bodies for request methods that aren't supported
|
548 |
+
* natively by PHP. In PHP 5.x, only POST has these parsed automatically.
|
549 |
+
*/
|
550 |
+
protected function parse_body_params() {
|
551 |
+
if ( $this->parsed_body ) {
|
552 |
+
return;
|
553 |
+
}
|
554 |
+
$this->parsed_body = true;
|
555 |
+
|
556 |
+
// Check that we got URL-encoded. Treat a missing content-type as
|
557 |
+
// URL-encoded for maximum compatibility
|
558 |
+
$content_type = $this->get_content_type();
|
559 |
+
if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' !== $content_type['value'] ) {
|
560 |
+
return;
|
561 |
+
}
|
562 |
+
|
563 |
+
parse_str( $this->get_body(), $params );
|
564 |
+
|
565 |
+
// Amazingly, parse_str follows magic quote rules. Sigh.
|
566 |
+
// NOTE: Do not refactor to use `wp_unslash`.
|
567 |
+
// @codeCoverageIgnoreStart
|
568 |
+
if ( get_magic_quotes_gpc() ) {
|
569 |
+
$params = stripslashes_deep( $params );
|
570 |
+
}
|
571 |
+
// @codeCoverageIgnoreEnd
|
572 |
+
|
573 |
+
// Add to the POST parameters stored internally. If a user has already
|
574 |
+
// set these manually (via `set_body_params`), don't override them.
|
575 |
+
$this->params['POST'] = array_merge( $params, $this->params['POST'] );
|
576 |
+
}
|
577 |
+
|
578 |
+
/**
|
579 |
+
* Get route that matched the request
|
580 |
+
*
|
581 |
+
* @return string Route matching regex
|
582 |
+
*/
|
583 |
+
public function get_route() {
|
584 |
+
return $this->route;
|
585 |
+
}
|
586 |
+
|
587 |
+
/**
|
588 |
+
* Set route that matched the request
|
589 |
+
*
|
590 |
+
* @param string $route Route matching regex
|
591 |
+
*/
|
592 |
+
public function set_route( $route ) {
|
593 |
+
$this->route = $route;
|
594 |
+
}
|
595 |
+
|
596 |
+
/**
|
597 |
+
* Get attributes for the request
|
598 |
+
*
|
599 |
+
* These are the options for the route that was matched.
|
600 |
+
*
|
601 |
+
* @return array Attributes for the request
|
602 |
+
*/
|
603 |
+
public function get_attributes() {
|
604 |
+
return $this->attributes;
|
605 |
+
}
|
606 |
+
|
607 |
+
/**
|
608 |
+
* Set attributes for the request
|
609 |
+
*
|
610 |
+
* @param array $attributes Attributes for the request
|
611 |
+
*/
|
612 |
+
public function set_attributes( $attributes ) {
|
613 |
+
$this->attributes = $attributes;
|
614 |
+
}
|
615 |
+
|
616 |
+
/**
|
617 |
+
* Sanitize (where possible) the params on the request.
|
618 |
+
*
|
619 |
+
* This is primarily based off the sanitize_callback param on each registered
|
620 |
+
* argument.
|
621 |
+
*
|
622 |
+
* @return null
|
623 |
+
*/
|
624 |
+
public function sanitize_params() {
|
625 |
+
|
626 |
+
$attributes = $this->get_attributes();
|
627 |
+
|
628 |
+
// No arguments set, skip sanitizing
|
629 |
+
if ( empty( $attributes['args'] ) ) {
|
630 |
+
return true;
|
631 |
+
}
|
632 |
+
|
633 |
+
$order = $this->get_parameter_order();
|
634 |
+
|
635 |
+
foreach ( $order as $type ) {
|
636 |
+
if ( empty( $this->params[ $type ] ) ) {
|
637 |
+
continue;
|
638 |
+
}
|
639 |
+
foreach ( $this->params[ $type ] as $key => $value ) {
|
640 |
+
// check if this param has a sanitize_callback added
|
641 |
+
if ( isset( $attributes['args'][ $key ] ) && ! empty( $attributes['args'][ $key ]['sanitize_callback'] ) ) {
|
642 |
+
$this->params[ $type ][ $key ] = call_user_func( $attributes['args'][ $key ]['sanitize_callback'], $value, $this, $key );
|
643 |
+
}
|
644 |
+
}
|
645 |
+
}
|
646 |
+
}
|
647 |
+
|
648 |
+
/**
|
649 |
+
* Check whether this request is valid according to its attributes
|
650 |
+
*
|
651 |
+
* @return bool|WP_Error
|
652 |
+
*/
|
653 |
+
public function has_valid_params() {
|
654 |
+
|
655 |
+
$attributes = $this->get_attributes();
|
656 |
+
$required = array();
|
657 |
+
|
658 |
+
// No arguments set, skip validation
|
659 |
+
if ( empty( $attributes['args'] ) ) {
|
660 |
+
return true;
|
661 |
+
}
|
662 |
+
|
663 |
+
foreach ( $attributes['args'] as $key => $arg ) {
|
664 |
+
|
665 |
+
$param = $this->get_param( $key );
|
666 |
+
if ( isset( $arg['required'] ) && true === $arg['required'] && null === $param ) {
|
667 |
+
$required[] = $key;
|
668 |
+
}
|
669 |
+
}
|
670 |
+
|
671 |
+
if ( ! empty( $required ) ) {
|
672 |
+
return new WP_Error( 'rest_missing_callback_param', sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), array( 'status' => 400, 'params' => $required ) );
|
673 |
+
}
|
674 |
+
|
675 |
+
// check the validation callbacks for each registered arg.
|
676 |
+
// This is done after required checking as required checking is cheaper.
|
677 |
+
$invalid_params = array();
|
678 |
+
|
679 |
+
foreach ( $attributes['args'] as $key => $arg ) {
|
680 |
+
|
681 |
+
$param = $this->get_param( $key );
|
682 |
+
|
683 |
+
if ( null !== $param && ! empty( $arg['validate_callback']) ) {
|
684 |
+
$valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
|
685 |
+
|
686 |
+
if ( false === $valid_check ) {
|
687 |
+
$invalid_params[ $key ] = __( 'Invalid param.' );
|
688 |
+
}
|
689 |
+
|
690 |
+
if ( is_wp_error( $valid_check ) ) {
|
691 |
+
$invalid_params[] = sprintf( '%s (%s)', $key, $valid_check->get_error_message() );
|
692 |
+
}
|
693 |
+
}
|
694 |
+
}
|
695 |
+
|
696 |
+
if ( $invalid_params ) {
|
697 |
+
return new WP_Error( 'rest_invalid_param', sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', $invalid_params ) ), array( 'status' => 400, 'params' => $invalid_params ) );
|
698 |
+
}
|
699 |
+
|
700 |
+
return true;
|
701 |
+
|
702 |
+
}
|
703 |
+
|
704 |
+
/**
|
705 |
+
* Check if a parameter is set
|
706 |
+
*
|
707 |
+
* @param string $key Parameter name
|
708 |
+
* @return boolean
|
709 |
+
*/
|
710 |
+
// @codingStandardsIgnoreStart
|
711 |
+
public function offsetExists( $offset ) {
|
712 |
+
// @codingStandardsIgnoreEnd
|
713 |
+
$order = $this->get_parameter_order();
|
714 |
+
|
715 |
+
foreach ( $order as $type ) {
|
716 |
+
if ( isset( $this->params[ $type ][ $offset ] ) ) {
|
717 |
+
return true;
|
718 |
+
}
|
719 |
+
}
|
720 |
+
|
721 |
+
return false;
|
722 |
+
}
|
723 |
+
|
724 |
+
/**
|
725 |
+
* Get a parameter from the request
|
726 |
+
*
|
727 |
+
* @param string $key Parameter name
|
728 |
+
* @return mixed|null Value if set, null otherwise
|
729 |
+
*/
|
730 |
+
// @codingStandardsIgnoreStart
|
731 |
+
public function offsetGet( $offset ) {
|
732 |
+
// @codingStandardsIgnoreEnd
|
733 |
+
return $this->get_param( $offset );
|
734 |
+
}
|
735 |
+
|
736 |
+
/**
|
737 |
+
* Set a parameter on the request
|
738 |
+
*
|
739 |
+
* @param string $key Parameter name
|
740 |
+
* @param mixed $value Parameter value
|
741 |
+
*/
|
742 |
+
// @codingStandardsIgnoreStart
|
743 |
+
public function offsetSet( $offset, $value ) {
|
744 |
+
// @codingStandardsIgnoreEnd
|
745 |
+
return $this->set_param( $offset, $value );
|
746 |
+
}
|
747 |
+
|
748 |
+
/**
|
749 |
+
* Remove a parameter from the request
|
750 |
+
*
|
751 |
+
* @param string $key Parameter name
|
752 |
+
* @param mixed $value Parameter value
|
753 |
+
*/
|
754 |
+
// @codingStandardsIgnoreStart
|
755 |
+
public function offsetUnset( $offset ) {
|
756 |
+
// @codingStandardsIgnoreEnd
|
757 |
+
$order = $this->get_parameter_order();
|
758 |
+
|
759 |
+
// Remove the offset from every group
|
760 |
+
foreach ( $order as $type ) {
|
761 |
+
unset( $this->params[ $type ][ $offset ] );
|
762 |
+
}
|
763 |
+
}
|
764 |
+
}
|
lib/infrastructure/class-wp-rest-response.php
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_REST_Response extends WP_HTTP_Response {
|
4 |
+
/**
|
5 |
+
* Links related to the response
|
6 |
+
*
|
7 |
+
* @var array
|
8 |
+
*/
|
9 |
+
protected $links = array();
|
10 |
+
|
11 |
+
/**
|
12 |
+
* The route that was to create the response
|
13 |
+
*
|
14 |
+
* @var string
|
15 |
+
*/
|
16 |
+
protected $matched_route = '';
|
17 |
+
|
18 |
+
/**
|
19 |
+
* The handler that was used to create the response
|
20 |
+
*
|
21 |
+
* @var null|array
|
22 |
+
*/
|
23 |
+
protected $matched_handler = null;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Add a link to the response
|
27 |
+
*
|
28 |
+
* @internal The $rel parameter is first, as this looks nicer when sending multiple
|
29 |
+
*
|
30 |
+
* @link http://tools.ietf.org/html/rfc5988
|
31 |
+
* @link http://www.iana.org/assignments/link-relations/link-relations.xml
|
32 |
+
*
|
33 |
+
* @param string $rel Link relation. Either an IANA registered type, or an absolute URL
|
34 |
+
* @param string $link Target IRI for the link
|
35 |
+
* @param array $attributes Link parameters to send along with the URL
|
36 |
+
*/
|
37 |
+
public function add_link( $rel, $href, $attributes = array() ) {
|
38 |
+
if ( empty( $this->links[ $rel ] ) ) {
|
39 |
+
$this->links[ $rel ] = array();
|
40 |
+
}
|
41 |
+
|
42 |
+
if ( isset( $attributes['href'] ) ) {
|
43 |
+
// Remove the href attribute, as it's used for the main URL
|
44 |
+
unset( $attributes['href'] );
|
45 |
+
}
|
46 |
+
|
47 |
+
$this->links[ $rel ][] = array(
|
48 |
+
'href' => $href,
|
49 |
+
'attributes' => $attributes,
|
50 |
+
);
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Add multiple links to the response.
|
55 |
+
*
|
56 |
+
* Link data should be an associative array with link relation as the key.
|
57 |
+
* The value can either be an associative array of link attributes
|
58 |
+
* (including `href` with the URL for the response), or a list of these
|
59 |
+
* associative arrays.
|
60 |
+
*
|
61 |
+
* @param array $links Map of link relation to list of links.
|
62 |
+
*/
|
63 |
+
public function add_links( $links ) {
|
64 |
+
foreach ( $links as $rel => $set ) {
|
65 |
+
// If it's a single link, wrap with an array for consistent handling
|
66 |
+
if ( isset( $set['href'] ) ) {
|
67 |
+
$set = array( $set );
|
68 |
+
}
|
69 |
+
|
70 |
+
foreach ( $set as $attributes ) {
|
71 |
+
$this->add_link( $rel, $attributes['href'], $attributes );
|
72 |
+
}
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Get links for the response
|
78 |
+
*
|
79 |
+
* @return array
|
80 |
+
*/
|
81 |
+
public function get_links() {
|
82 |
+
return $this->links;
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Set a single link header
|
87 |
+
*
|
88 |
+
* @internal The $rel parameter is first, as this looks nicer when sending multiple
|
89 |
+
*
|
90 |
+
* @link http://tools.ietf.org/html/rfc5988
|
91 |
+
* @link http://www.iana.org/assignments/link-relations/link-relations.xml
|
92 |
+
*
|
93 |
+
* @param string $rel Link relation. Either an IANA registered type, or an absolute URL
|
94 |
+
* @param string $link Target IRI for the link
|
95 |
+
* @param array $other Other parameters to send, as an assocative array
|
96 |
+
*/
|
97 |
+
public function link_header( $rel, $link, $other = array() ) {
|
98 |
+
$header = '<' . $link . '>; rel="' . $rel . '"';
|
99 |
+
|
100 |
+
foreach ( $other as $key => $value ) {
|
101 |
+
if ( 'title' === $key ) {
|
102 |
+
$value = '"' . $value . '"';
|
103 |
+
}
|
104 |
+
$header .= '; ' . $key . '=' . $value;
|
105 |
+
}
|
106 |
+
return $this->header( 'Link', $header, false );
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Get the route that was used to
|
111 |
+
*
|
112 |
+
* @return string
|
113 |
+
*/
|
114 |
+
public function get_matched_route() {
|
115 |
+
return $this->matched_route;
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Set the route (regex for path) that caused the response
|
120 |
+
*
|
121 |
+
* @param string $route
|
122 |
+
*/
|
123 |
+
public function set_matched_route( $route ) {
|
124 |
+
$this->matched_route = $route;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Get the handler that was used to generate the response
|
129 |
+
*
|
130 |
+
* @return null|array
|
131 |
+
*/
|
132 |
+
public function get_matched_handler() {
|
133 |
+
return $this->matched_handler;
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Get the handler that was responsible for generting the response
|
138 |
+
*
|
139 |
+
* @param array $handler
|
140 |
+
*/
|
141 |
+
public function set_matched_handler( $handler ) {
|
142 |
+
$this->matched_handler = $handler;
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Check if the response is an error, i.e. >= 400 response code
|
147 |
+
*
|
148 |
+
* @return boolean
|
149 |
+
*/
|
150 |
+
public function is_error() {
|
151 |
+
return $this->get_status() >= 400;
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Get a WP_Error object from the response's
|
156 |
+
*
|
157 |
+
* @return WP_Error|null on not an errored response
|
158 |
+
*/
|
159 |
+
public function as_error() {
|
160 |
+
if ( ! $this->is_error() ) {
|
161 |
+
return null;
|
162 |
+
}
|
163 |
+
|
164 |
+
$error = new WP_Error;
|
165 |
+
|
166 |
+
if ( is_array( $this->get_data() ) ) {
|
167 |
+
foreach ( $this->get_data() as $err ) {
|
168 |
+
$error->add( $err['code'], $err['message'], $err['data'] );
|
169 |
+
}
|
170 |
+
} else {
|
171 |
+
$error->add( $this->get_status(), '', array( 'status' => $this->get_status() ) );
|
172 |
+
}
|
173 |
+
|
174 |
+
return $error;
|
175 |
+
}
|
176 |
+
}
|
lib/infrastructure/class-wp-rest-server.php
ADDED
@@ -0,0 +1,962 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* WordPress REST API
|
4 |
+
*
|
5 |
+
* Contains the WP_REST_Server class.
|
6 |
+
*
|
7 |
+
* @package WordPress
|
8 |
+
*/
|
9 |
+
|
10 |
+
require_once ( ABSPATH . 'wp-admin/includes/admin.php' );
|
11 |
+
|
12 |
+
/**
|
13 |
+
* WordPress REST API server handler
|
14 |
+
*
|
15 |
+
* @package WordPress
|
16 |
+
*/
|
17 |
+
class WP_REST_Server {
|
18 |
+
const METHOD_GET = 'GET';
|
19 |
+
const METHOD_POST = 'POST';
|
20 |
+
const METHOD_PUT = 'PUT';
|
21 |
+
const METHOD_PATCH = 'PATCH';
|
22 |
+
const METHOD_DELETE = 'DELETE';
|
23 |
+
|
24 |
+
const READABLE = 'GET';
|
25 |
+
const CREATABLE = 'POST';
|
26 |
+
const EDITABLE = 'POST, PUT, PATCH';
|
27 |
+
const DELETABLE = 'DELETE';
|
28 |
+
const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Does the endpoint accept raw JSON entities?
|
32 |
+
*/
|
33 |
+
const ACCEPT_RAW = 64;
|
34 |
+
const ACCEPT_JSON = 128;
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Should we hide this endpoint from the index?
|
38 |
+
*/
|
39 |
+
const HIDDEN_ENDPOINT = 256;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Map of HTTP verbs to constants
|
43 |
+
* @var array
|
44 |
+
*/
|
45 |
+
public static $method_map = array(
|
46 |
+
'HEAD' => self::METHOD_GET,
|
47 |
+
'GET' => self::METHOD_GET,
|
48 |
+
'POST' => self::METHOD_POST,
|
49 |
+
'PUT' => self::METHOD_PUT,
|
50 |
+
'PATCH' => self::METHOD_PATCH,
|
51 |
+
'DELETE' => self::METHOD_DELETE,
|
52 |
+
);
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Namespaces registered to the server
|
56 |
+
*
|
57 |
+
* @var array
|
58 |
+
*/
|
59 |
+
protected $namespaces = array();
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Endpoints registered to the server
|
63 |
+
*
|
64 |
+
* @var array
|
65 |
+
*/
|
66 |
+
protected $endpoints = array();
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Options defined for the routes
|
70 |
+
*
|
71 |
+
* @var array
|
72 |
+
*/
|
73 |
+
protected $route_options = array();
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Instantiate the server
|
77 |
+
*/
|
78 |
+
public function __construct() {
|
79 |
+
$this->endpoints = array(
|
80 |
+
// Meta endpoints
|
81 |
+
'/' => array(
|
82 |
+
'callback' => array( $this, 'get_index' ),
|
83 |
+
'methods' => 'GET',
|
84 |
+
),
|
85 |
+
);
|
86 |
+
}
|
87 |
+
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Check the authentication headers if supplied
|
91 |
+
*
|
92 |
+
* @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful or no authentication provided
|
93 |
+
*/
|
94 |
+
public function check_authentication() {
|
95 |
+
/**
|
96 |
+
* Pass an authentication error to the API
|
97 |
+
*
|
98 |
+
* This is used to pass a {@see WP_Error} from an authentication method
|
99 |
+
* back to the API.
|
100 |
+
*
|
101 |
+
* Authentication methods should check first if they're being used, as
|
102 |
+
* multiple authentication methods can be enabled on a site (cookies,
|
103 |
+
* HTTP basic auth, OAuth). If the authentication method hooked in is
|
104 |
+
* not actually being attempted, null should be returned to indicate
|
105 |
+
* another authentication method should check instead. Similarly,
|
106 |
+
* callbacks should ensure the value is `null` before checking for
|
107 |
+
* errors.
|
108 |
+
*
|
109 |
+
* A {@see WP_Error} instance can be returned if an error occurs, and
|
110 |
+
* this should match the format used by API methods internally (that is,
|
111 |
+
* the `status` data should be used). A callback can return `true` to
|
112 |
+
* indicate that the authentication method was used, and it succeeded.
|
113 |
+
*
|
114 |
+
* @param WP_Error|null|boolean WP_Error if authentication error, null if authentication method wasn't used, true if authentication succeeded
|
115 |
+
*/
|
116 |
+
return apply_filters( 'rest_authentication_errors', null );
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Convert an error to a response object
|
121 |
+
*
|
122 |
+
* This iterates over all error codes and messages to change it into a flat
|
123 |
+
* array. This enables simpler client behaviour, as it is represented as a
|
124 |
+
* list in JSON rather than an object/map
|
125 |
+
*
|
126 |
+
* @param WP_Error $error
|
127 |
+
* @return array List of associative arrays with code and message keys
|
128 |
+
*/
|
129 |
+
protected function error_to_response( $error ) {
|
130 |
+
$error_data = $error->get_error_data();
|
131 |
+
if ( is_array( $error_data ) && isset( $error_data['status'] ) ) {
|
132 |
+
$status = $error_data['status'];
|
133 |
+
} else {
|
134 |
+
$status = 500;
|
135 |
+
}
|
136 |
+
|
137 |
+
$data = array();
|
138 |
+
foreach ( (array) $error->errors as $code => $messages ) {
|
139 |
+
foreach ( (array) $messages as $message ) {
|
140 |
+
$data[] = array( 'code' => $code, 'message' => $message, 'data' => $error->get_error_data( $code ) );
|
141 |
+
}
|
142 |
+
}
|
143 |
+
$response = new WP_REST_Response( $data, $status );
|
144 |
+
|
145 |
+
return $response;
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Get an appropriate error representation in JSON
|
150 |
+
*
|
151 |
+
* Note: This should only be used in {@see WP_REST_Server::serve_request()},
|
152 |
+
* as it cannot handle WP_Error internally. All callbacks and other internal
|
153 |
+
* methods should instead return a WP_Error with the data set to an array
|
154 |
+
* that includes a 'status' key, with the value being the HTTP status to
|
155 |
+
* send.
|
156 |
+
*
|
157 |
+
* @param string $code WP_Error-style code
|
158 |
+
* @param string $message Human-readable message
|
159 |
+
* @param int $status HTTP status code to send
|
160 |
+
* @return string JSON representation of the error
|
161 |
+
*/
|
162 |
+
protected function json_error( $code, $message, $status = null ) {
|
163 |
+
if ( $status ) {
|
164 |
+
$this->set_status( $status );
|
165 |
+
}
|
166 |
+
$error = compact( 'code', 'message' );
|
167 |
+
|
168 |
+
return json_encode( array( $error ) );
|
169 |
+
}
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Handle serving an API request
|
173 |
+
*
|
174 |
+
* Matches the current server URI to a route and runs the first matching
|
175 |
+
* callback then outputs a JSON representation of the returned value.
|
176 |
+
*
|
177 |
+
* @uses WP_REST_Server::dispatch()
|
178 |
+
*/
|
179 |
+
public function serve_request( $path = null ) {
|
180 |
+
$content_type = isset( $_GET['_jsonp'] ) ? 'application/javascript' : 'application/json';
|
181 |
+
$this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
|
182 |
+
|
183 |
+
// Mitigate possible JSONP Flash attacks
|
184 |
+
// http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
|
185 |
+
$this->send_header( 'X-Content-Type-Options', 'nosniff' );
|
186 |
+
|
187 |
+
// Proper filter for turning off the JSON API. It is on by default.
|
188 |
+
$enabled = apply_filters( 'rest_enabled', true );
|
189 |
+
|
190 |
+
$jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true );
|
191 |
+
|
192 |
+
if ( ! $enabled ) {
|
193 |
+
echo $this->json_error( 'rest_disabled', __( 'The REST API is disabled on this site.' ), 404 );
|
194 |
+
return false;
|
195 |
+
}
|
196 |
+
if ( isset( $_GET['_jsonp'] ) ) {
|
197 |
+
if ( ! $jsonp_enabled ) {
|
198 |
+
echo $this->json_error( 'rest_callback_disabled', __( 'JSONP support is disabled on this site.' ), 400 );
|
199 |
+
return false;
|
200 |
+
}
|
201 |
+
|
202 |
+
// Check for invalid characters (only alphanumeric allowed)
|
203 |
+
if ( ! is_string( $_GET['_jsonp'] ) || preg_match( '/\W\./', $_GET['_jsonp'] ) ) {
|
204 |
+
echo $this->json_error( 'rest_callback_invalid', __( 'The JSONP callback function is invalid.' ), 400 );
|
205 |
+
return false;
|
206 |
+
}
|
207 |
+
}
|
208 |
+
|
209 |
+
if ( empty( $path ) ) {
|
210 |
+
if ( isset( $_SERVER['PATH_INFO'] ) ) {
|
211 |
+
$path = $_SERVER['PATH_INFO'];
|
212 |
+
} else {
|
213 |
+
$path = '/';
|
214 |
+
}
|
215 |
+
}
|
216 |
+
|
217 |
+
$request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path );
|
218 |
+
$request->set_query_params( $_GET );
|
219 |
+
$request->set_body_params( $_POST );
|
220 |
+
$request->set_file_params( $_FILES );
|
221 |
+
$request->set_headers( $this->get_headers( $_SERVER ) );
|
222 |
+
$request->set_body( $this->get_raw_data() );
|
223 |
+
|
224 |
+
/**
|
225 |
+
* HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
|
226 |
+
* $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
|
227 |
+
* header.
|
228 |
+
*/
|
229 |
+
if ( isset( $_GET['_method'] ) ) {
|
230 |
+
$request->set_method( $_GET['_method'] );
|
231 |
+
} elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
|
232 |
+
$request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
|
233 |
+
}
|
234 |
+
|
235 |
+
$result = $this->check_authentication();
|
236 |
+
|
237 |
+
if ( ! is_wp_error( $result ) ) {
|
238 |
+
/**
|
239 |
+
* Allow hijacking the request before dispatching
|
240 |
+
*
|
241 |
+
* If `$result` is non-empty, this value will be used to serve the
|
242 |
+
* request instead.
|
243 |
+
*
|
244 |
+
* @param mixed $result Response to replace the requested version with. Can be anything a normal endpoint can return, or null to not hijack the request.
|
245 |
+
* @param WP_REST_Server $this Server instance
|
246 |
+
* @param WP_REST_Request $request Request used to generate the response
|
247 |
+
*/
|
248 |
+
$result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
|
249 |
+
}
|
250 |
+
|
251 |
+
if ( empty( $result ) ) {
|
252 |
+
$result = $this->dispatch( $request );
|
253 |
+
}
|
254 |
+
|
255 |
+
// Normalize to either WP_Error or WP_REST_Response...
|
256 |
+
$result = rest_ensure_response( $result );
|
257 |
+
|
258 |
+
// ...then convert WP_Error across
|
259 |
+
if ( is_wp_error( $result ) ) {
|
260 |
+
$result = $this->error_to_response( $result );
|
261 |
+
}
|
262 |
+
|
263 |
+
/**
|
264 |
+
* Allow modifying the response before returning
|
265 |
+
*
|
266 |
+
* @param WP_HTTP_ResponseInterface $result Result to send to the client. Usually a WP_REST_Response
|
267 |
+
* @param WP_REST_Server $this Server instance
|
268 |
+
* @param WP_REST_Request $request Request used to generate the response
|
269 |
+
*/
|
270 |
+
$result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request );
|
271 |
+
|
272 |
+
// Wrap the response in an envelope if asked for
|
273 |
+
if ( isset( $_GET['_envelope'] ) ) {
|
274 |
+
$result = $this->envelope_response( $result, isset( $_GET['_embed'] ) );
|
275 |
+
}
|
276 |
+
|
277 |
+
// Send extra data from response objects
|
278 |
+
$headers = $result->get_headers();
|
279 |
+
$this->send_headers( $headers );
|
280 |
+
|
281 |
+
$code = $result->get_status();
|
282 |
+
$this->set_status( $code );
|
283 |
+
|
284 |
+
/**
|
285 |
+
* Allow sending the request manually
|
286 |
+
*
|
287 |
+
* If `$served` is true, the result will not be sent to the client.
|
288 |
+
*
|
289 |
+
* This is a filter rather than an action, since this is designed to be
|
290 |
+
* re-entrant if needed.
|
291 |
+
*
|
292 |
+
* @param bool $served Whether the request has already been served
|
293 |
+
* @param WP_HTTP_ResponseInterface $result Result to send to the client. Usually a WP_REST_Response
|
294 |
+
* @param WP_REST_Request $request Request used to generate the response
|
295 |
+
* @param WP_REST_Server $this Server instance
|
296 |
+
*/
|
297 |
+
$served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );
|
298 |
+
|
299 |
+
if ( ! $served ) {
|
300 |
+
if ( 'HEAD' === $request->get_method() ) {
|
301 |
+
return;
|
302 |
+
}
|
303 |
+
|
304 |
+
// Embed links inside the request
|
305 |
+
$result = $this->response_to_data( $result, isset( $_GET['_embed'] ) );
|
306 |
+
|
307 |
+
$result = json_encode( $result );
|
308 |
+
|
309 |
+
$json_error_message = $this->get_json_last_error();
|
310 |
+
if ( $json_error_message ) {
|
311 |
+
$json_error_obj = new WP_Error( 'rest_encode_error', $json_error_message, array( 'status' => 500 ) );
|
312 |
+
$result = $this->error_to_response( $json_error_obj );
|
313 |
+
$result = json_encode( $result->data[0] );
|
314 |
+
}
|
315 |
+
|
316 |
+
if ( isset( $_GET['_jsonp'] ) ) {
|
317 |
+
// Prepend '/**/' to mitigate possible JSONP Flash attacks
|
318 |
+
// http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
|
319 |
+
echo '/**/' . $_GET['_jsonp'] . '(' . $result . ')';
|
320 |
+
} else {
|
321 |
+
echo $result;
|
322 |
+
}
|
323 |
+
}
|
324 |
+
}
|
325 |
+
|
326 |
+
/**
|
327 |
+
* Convert a response to data to send
|
328 |
+
*
|
329 |
+
* @param WP_REST_Response $response Response object
|
330 |
+
* @param boolean $embed Should we embed links?
|
331 |
+
* @return array
|
332 |
+
*/
|
333 |
+
public function response_to_data( $response, $embed ) {
|
334 |
+
$data = $this->prepare_response( $response->get_data() );
|
335 |
+
$links = $this->get_response_links( $response );
|
336 |
+
|
337 |
+
if ( ! empty( $links ) ) {
|
338 |
+
// Convert links to part of the data
|
339 |
+
$data['_links'] = $links;
|
340 |
+
|
341 |
+
if ( $embed ) {
|
342 |
+
$data = $this->embed_links( $data );
|
343 |
+
}
|
344 |
+
}
|
345 |
+
|
346 |
+
return $data;
|
347 |
+
}
|
348 |
+
|
349 |
+
/**
|
350 |
+
* Get links from a response.
|
351 |
+
*
|
352 |
+
* Extracts the links from a reponse into a structured hash, suitable for
|
353 |
+
* direct output.
|
354 |
+
*
|
355 |
+
* @param WP_REST_Response $response Response to extract links from.
|
356 |
+
* @return array Map of link relation to list of link hashes.
|
357 |
+
*/
|
358 |
+
public static function get_response_links( $response ) {
|
359 |
+
$links = $response->get_links();
|
360 |
+
|
361 |
+
if ( empty( $links ) ) {
|
362 |
+
return array();
|
363 |
+
}
|
364 |
+
|
365 |
+
// Convert links to part of the data
|
366 |
+
$data = array();
|
367 |
+
foreach ( $links as $rel => $items ) {
|
368 |
+
$data[ $rel ] = array();
|
369 |
+
|
370 |
+
foreach ( $items as $item ) {
|
371 |
+
$attributes = $item['attributes'];
|
372 |
+
$attributes['href'] = $item['href'];
|
373 |
+
$data[ $rel ][] = $attributes;
|
374 |
+
}
|
375 |
+
}
|
376 |
+
|
377 |
+
return $data;
|
378 |
+
}
|
379 |
+
|
380 |
+
/**
|
381 |
+
* Embed the links from the data into the request
|
382 |
+
*
|
383 |
+
* @param array $data Data from the request
|
384 |
+
* @return array Data with sub-requests embedded
|
385 |
+
*/
|
386 |
+
protected function embed_links( $data ) {
|
387 |
+
if ( empty( $data['_links'] ) ) {
|
388 |
+
return $data;
|
389 |
+
}
|
390 |
+
|
391 |
+
$embedded = array();
|
392 |
+
$api_root = rest_url();
|
393 |
+
foreach ( $data['_links'] as $rel => $links ) {
|
394 |
+
// Ignore links to self, for obvious reasons
|
395 |
+
if ( 'self' === $rel ) {
|
396 |
+
continue;
|
397 |
+
}
|
398 |
+
|
399 |
+
$embeds = array();
|
400 |
+
|
401 |
+
foreach ( $links as $item ) {
|
402 |
+
// Is the link embeddable?
|
403 |
+
if ( empty( $item['embeddable'] ) || strpos( $item['href'], $api_root ) !== 0 ) {
|
404 |
+
// Ensure we keep the same order
|
405 |
+
$embeds[] = array();
|
406 |
+
continue;
|
407 |
+
}
|
408 |
+
|
409 |
+
// Run through our internal routing and serve
|
410 |
+
$route = substr( $item['href'], strlen( untrailingslashit( $api_root ) ) );
|
411 |
+
$query_params = array();
|
412 |
+
|
413 |
+
// Parse out URL query parameters
|
414 |
+
$parsed = parse_url( $route );
|
415 |
+
if ( empty( $parsed['path'] ) ) {
|
416 |
+
$embeds[] = array();
|
417 |
+
continue;
|
418 |
+
}
|
419 |
+
|
420 |
+
if ( ! empty( $parsed['query'] ) ) {
|
421 |
+
parse_str( $parsed['query'], $query_params );
|
422 |
+
|
423 |
+
// Ensure magic quotes are stripped
|
424 |
+
// @codeCoverageIgnoreStart
|
425 |
+
if ( get_magic_quotes_gpc() ) {
|
426 |
+
$query_params = stripslashes_deep( $query_params );
|
427 |
+
}
|
428 |
+
// @codeCoverageIgnoreEnd
|
429 |
+
}
|
430 |
+
|
431 |
+
// Embedded resources get passed context=embed
|
432 |
+
$query_params['context'] = 'embed';
|
433 |
+
|
434 |
+
$request = new WP_REST_Request( 'GET', $parsed['path'] );
|
435 |
+
$request->set_query_params( $query_params );
|
436 |
+
$response = $this->dispatch( $request );
|
437 |
+
|
438 |
+
$embeds[] = $response;
|
439 |
+
}
|
440 |
+
|
441 |
+
// Did we get any real links?
|
442 |
+
$has_links = count( array_filter( $embeds ) );
|
443 |
+
if ( $has_links ) {
|
444 |
+
$embedded[ $rel ] = $embeds;
|
445 |
+
}
|
446 |
+
}
|
447 |
+
|
448 |
+
if ( ! empty( $embedded ) ) {
|
449 |
+
$data['_embedded'] = $embedded;
|
450 |
+
}
|
451 |
+
|
452 |
+
return $data;
|
453 |
+
}
|
454 |
+
|
455 |
+
/**
|
456 |
+
* Wrap the response in an envelope
|
457 |
+
*
|
458 |
+
* The enveloping technique is used to work around browser/client
|
459 |
+
* compatibility issues. Essentially, it converts the full HTTP response to
|
460 |
+
* data instead.
|
461 |
+
*
|
462 |
+
* @param WP_REST_Response $response Response object
|
463 |
+
* @param boolean $embed Should we embed links?
|
464 |
+
* @return WP_REST_Response New reponse with wrapped data
|
465 |
+
*/
|
466 |
+
public function envelope_response( $response, $embed ) {
|
467 |
+
$envelope = array(
|
468 |
+
'body' => $this->response_to_data( $response, $embed ),
|
469 |
+
'status' => $response->get_status(),
|
470 |
+
'headers' => $response->get_headers(),
|
471 |
+
);
|
472 |
+
|
473 |
+
/**
|
474 |
+
* Alter the enveloped form of a response
|
475 |
+
*
|
476 |
+
* @param array $envelope Envelope data
|
477 |
+
* @param WP_REST_Response $response Original response data
|
478 |
+
*/
|
479 |
+
$envelope = apply_filters( 'rest_envelope_response', $envelope, $response );
|
480 |
+
|
481 |
+
// Ensure it's still a response
|
482 |
+
return rest_ensure_response( $envelope );
|
483 |
+
}
|
484 |
+
|
485 |
+
/**
|
486 |
+
* Register a route to the server
|
487 |
+
*
|
488 |
+
* @param string $route
|
489 |
+
* @param array $route_args
|
490 |
+
* @param boolean $override If the route already exists, should we override it? True overrides, false merges (with newer overriding if duplicate keys exist)
|
491 |
+
*/
|
492 |
+
public function register_route( $namespace, $route, $route_args, $override = false ) {
|
493 |
+
if ( ! isset( $this->namespaces[ $namespace ] ) ) {
|
494 |
+
$this->namespaces[ $namespace ] = array();
|
495 |
+
|
496 |
+
$this->register_route( $namespace, '/' . $namespace, array(
|
497 |
+
array(
|
498 |
+
'methods' => self::READABLE,
|
499 |
+
'callback' => array( $this, 'get_namespace_index' ),
|
500 |
+
'args' => array(
|
501 |
+
'namespace' => array(
|
502 |
+
'default' => $namespace,
|
503 |
+
),
|
504 |
+
),
|
505 |
+
),
|
506 |
+
) );
|
507 |
+
}
|
508 |
+
|
509 |
+
// Associative to avoid double-registration
|
510 |
+
$this->namespaces[ $namespace ][ $route ] = true;
|
511 |
+
$route_args['namespace'] = $namespace;
|
512 |
+
|
513 |
+
if ( $override || empty( $this->endpoints[ $route ] ) ) {
|
514 |
+
$this->endpoints[ $route ] = $route_args;
|
515 |
+
} else {
|
516 |
+
$this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args );
|
517 |
+
}
|
518 |
+
}
|
519 |
+
|
520 |
+
/**
|
521 |
+
* Retrieve the route map
|
522 |
+
*
|
523 |
+
* The route map is an associative array with path regexes as the keys. The
|
524 |
+
* value is an indexed array with the callback function/method as the first
|
525 |
+
* item, and a bitmask of HTTP methods as the second item (see the class
|
526 |
+
* constants).
|
527 |
+
*
|
528 |
+
* Each route can be mapped to more than one callback by using an array of
|
529 |
+
* the indexed arrays. This allows mapping e.g. GET requests to one callback
|
530 |
+
* and POST requests to another.
|
531 |
+
*
|
532 |
+
* Note that the path regexes (array keys) must have @ escaped, as this is
|
533 |
+
* used as the delimiter with preg_match()
|
534 |
+
*
|
535 |
+
* @return array `'/path/regex' => array( $callback, $bitmask )` or `'/path/regex' => array( array( $callback, $bitmask ), ...)`
|
536 |
+
*/
|
537 |
+
public function get_routes() {
|
538 |
+
|
539 |
+
$endpoints = apply_filters( 'rest_endpoints', $this->endpoints );
|
540 |
+
|
541 |
+
// Normalise the endpoints
|
542 |
+
$defaults = array(
|
543 |
+
'methods' => '',
|
544 |
+
'accept_json' => false,
|
545 |
+
'accept_raw' => false,
|
546 |
+
'show_in_index' => true,
|
547 |
+
'args' => array(),
|
548 |
+
);
|
549 |
+
foreach ( $endpoints as $route => &$handlers ) {
|
550 |
+
if ( isset( $handlers['callback'] ) ) {
|
551 |
+
// Single endpoint, add one deeper
|
552 |
+
$handlers = array( $handlers );
|
553 |
+
}
|
554 |
+
if ( ! isset( $this->route_options[ $route ] ) ) {
|
555 |
+
$this->route_options[ $route ] = array();
|
556 |
+
}
|
557 |
+
|
558 |
+
foreach ( $handlers as $key => &$handler ) {
|
559 |
+
if ( ! is_numeric( $key ) ) {
|
560 |
+
// Route option, move it to the options
|
561 |
+
$this->route_options[ $route ][ $key ] = $handler;
|
562 |
+
unset( $handlers[ $key ] );
|
563 |
+
continue;
|
564 |
+
}
|
565 |
+
$handler = wp_parse_args( $handler, $defaults );
|
566 |
+
|
567 |
+
// Allow comma-separated HTTP methods
|
568 |
+
if ( is_string( $handler['methods'] ) ) {
|
569 |
+
$methods = explode( ',', $handler['methods'] );
|
570 |
+
} else if ( is_array( $handler['methods'] ) ) {
|
571 |
+
$methods = $handler['methods'];
|
572 |
+
}
|
573 |
+
|
574 |
+
$handler['methods'] = array();
|
575 |
+
foreach ( $methods as $method ) {
|
576 |
+
$method = strtoupper( trim( $method ) );
|
577 |
+
$handler['methods'][ $method ] = true;
|
578 |
+
}
|
579 |
+
}
|
580 |
+
}
|
581 |
+
return $endpoints;
|
582 |
+
}
|
583 |
+
|
584 |
+
/**
|
585 |
+
* Get namespaces registered on the server.
|
586 |
+
*
|
587 |
+
* @return array List of registered namespaces.
|
588 |
+
*/
|
589 |
+
public function get_namespaces() {
|
590 |
+
return array_keys( $this->namespaces );
|
591 |
+
}
|
592 |
+
|
593 |
+
/**
|
594 |
+
* Match the request to a callback and call it
|
595 |
+
*
|
596 |
+
* @param WP_REST_Request $request Request to attempt dispatching
|
597 |
+
* @return WP_REST_Response Response returned by the callback
|
598 |
+
*/
|
599 |
+
public function dispatch( $request ) {
|
600 |
+
$method = $request->get_method();
|
601 |
+
$path = $request->get_route();
|
602 |
+
|
603 |
+
foreach ( $this->get_routes() as $route => $handlers ) {
|
604 |
+
foreach ( $handlers as $handler ) {
|
605 |
+
$callback = $handler['callback'];
|
606 |
+
$supported = $handler['methods'];
|
607 |
+
$response = null;
|
608 |
+
|
609 |
+
if ( empty( $handler['methods'][ $method ] ) ) {
|
610 |
+
continue;
|
611 |
+
}
|
612 |
+
|
613 |
+
$match = preg_match( '@^' . $route . '$@i', $path, $args );
|
614 |
+
|
615 |
+
if ( ! $match ) {
|
616 |
+
continue;
|
617 |
+
}
|
618 |
+
|
619 |
+
if ( ! is_callable( $callback ) ) {
|
620 |
+
$response = new WP_Error( 'rest_invalid_handler', __( 'The handler for the route is invalid' ), array( 'status' => 500 ) );
|
621 |
+
}
|
622 |
+
|
623 |
+
if ( ! is_wp_error( $response ) ) {
|
624 |
+
|
625 |
+
$request->set_url_params( $args );
|
626 |
+
$request->set_attributes( $handler );
|
627 |
+
|
628 |
+
$request->sanitize_params();
|
629 |
+
|
630 |
+
$defaults = array();
|
631 |
+
|
632 |
+
foreach ( $handler['args'] as $arg => $options ) {
|
633 |
+
if ( isset( $options['default'] ) ) {
|
634 |
+
$defaults[ $arg ] = $options['default'];
|
635 |
+
}
|
636 |
+
}
|
637 |
+
|
638 |
+
$request->set_default_params( $defaults );
|
639 |
+
|
640 |
+
$check_required = $request->has_valid_params();
|
641 |
+
if ( is_wp_error( $check_required ) ) {
|
642 |
+
$response = $check_required;
|
643 |
+
}
|
644 |
+
}
|
645 |
+
|
646 |
+
if ( ! is_wp_error( $response ) ) {
|
647 |
+
// check permission specified on the route.
|
648 |
+
if ( ! empty( $handler['permission_callback'] ) ) {
|
649 |
+
$permission = call_user_func( $handler['permission_callback'], $request );
|
650 |
+
|
651 |
+
if ( is_wp_error( $permission ) ) {
|
652 |
+
$response = $permission;
|
653 |
+
} else if ( false === $permission || null === $permission ) {
|
654 |
+
$response = new WP_Error( 'rest_forbidden', __( "You don't have permission to do this." ), array( 'status' => 403 ) );
|
655 |
+
}
|
656 |
+
}
|
657 |
+
}
|
658 |
+
|
659 |
+
if ( ! is_wp_error( $response ) ) {
|
660 |
+
/**
|
661 |
+
* Allow plugins to override dispatching the request
|
662 |
+
*
|
663 |
+
* @param boolean $dispatch_result Dispatch result, will be used if not empty
|
664 |
+
* @param WP_REST_Request $request
|
665 |
+
*/
|
666 |
+
$dispatch_result = apply_filters( 'rest_dispatch_request', null, $request );
|
667 |
+
|
668 |
+
// Allow plugins to halt the request via this filter
|
669 |
+
if ( null !== $dispatch_result ) {
|
670 |
+
$response = $dispatch_result;
|
671 |
+
} else {
|
672 |
+
$response = call_user_func( $callback, $request );
|
673 |
+
}
|
674 |
+
}
|
675 |
+
|
676 |
+
if ( is_wp_error( $response ) ) {
|
677 |
+
$response = $this->error_to_response( $response );
|
678 |
+
} else {
|
679 |
+
$response = rest_ensure_response( $response );
|
680 |
+
}
|
681 |
+
|
682 |
+
$response->set_matched_route( $route );
|
683 |
+
$response->set_matched_handler( $handler );
|
684 |
+
|
685 |
+
return $response;
|
686 |
+
}
|
687 |
+
}
|
688 |
+
|
689 |
+
return $this->error_to_response( new WP_Error( 'rest_no_route', __( 'No route was found matching the URL and request method' ), array( 'status' => 404 ) ) );
|
690 |
+
}
|
691 |
+
|
692 |
+
/**
|
693 |
+
* Returns if an error occurred during most recent JSON encode/decode
|
694 |
+
* Strings to be translated will be in format like "Encoding error: Maximum stack depth exceeded"
|
695 |
+
*
|
696 |
+
* @return boolean|string Boolean false or string error message
|
697 |
+
*/
|
698 |
+
protected function get_json_last_error( ) {
|
699 |
+
// see https://core.trac.wordpress.org/ticket/27799
|
700 |
+
if ( ! function_exists( 'json_last_error' ) ) {
|
701 |
+
return false;
|
702 |
+
}
|
703 |
+
|
704 |
+
$last_error_code = json_last_error();
|
705 |
+
if ( ( defined( 'JSON_ERROR_NONE' ) && JSON_ERROR_NONE === $last_error_code ) || empty( $last_error_code ) ) {
|
706 |
+
return false;
|
707 |
+
}
|
708 |
+
|
709 |
+
return json_last_error_msg();
|
710 |
+
}
|
711 |
+
|
712 |
+
/**
|
713 |
+
* Get the site index.
|
714 |
+
*
|
715 |
+
* This endpoint describes the capabilities of the site.
|
716 |
+
*
|
717 |
+
* @todo Should we generate text documentation too based on PHPDoc?
|
718 |
+
*
|
719 |
+
* @return array Index entity
|
720 |
+
*/
|
721 |
+
public function get_index() {
|
722 |
+
// General site data
|
723 |
+
$available = array(
|
724 |
+
'name' => get_option( 'blogname' ),
|
725 |
+
'description' => get_option( 'blogdescription' ),
|
726 |
+
'url' => get_option( 'siteurl' ),
|
727 |
+
'namespaces' => array_keys( $this->namespaces ),
|
728 |
+
'authentication' => array(),
|
729 |
+
'routes' => $this->get_route_data( $this->get_routes() ),
|
730 |
+
);
|
731 |
+
|
732 |
+
$response = new WP_REST_Response( $available );
|
733 |
+
$response->add_link( 'help', 'http://v2.wp-api.org/' );
|
734 |
+
|
735 |
+
/**
|
736 |
+
* Filter the API root index data.
|
737 |
+
*
|
738 |
+
* This contains the data describing the API. This includes information
|
739 |
+
* about supported authentication schemes, supported namespaces, routes
|
740 |
+
* available on the API, and a small amount of data about the site.
|
741 |
+
*
|
742 |
+
* @param WP_REST_Response $response Response data.
|
743 |
+
*/
|
744 |
+
return apply_filters( 'rest_index', $response );
|
745 |
+
}
|
746 |
+
|
747 |
+
/**
|
748 |
+
* Get the index for a namespace.
|
749 |
+
*
|
750 |
+
* @param WP_REST_Request $request
|
751 |
+
* @return array|WP_REST_Response
|
752 |
+
*/
|
753 |
+
public function get_namespace_index( $request ) {
|
754 |
+
$namespace = $request['namespace'];
|
755 |
+
|
756 |
+
if ( ! isset( $this->namespaces[ $namespace ] ) ) {
|
757 |
+
return new WP_Error( 'rest_invalid_namespace', __( 'The specified namespace could not be found.' ), array( 'status' => 404 ) );
|
758 |
+
}
|
759 |
+
|
760 |
+
$routes = $this->namespaces[ $namespace ];
|
761 |
+
$endpoints = array_intersect_key( $this->get_routes(), $routes );
|
762 |
+
|
763 |
+
$data = array(
|
764 |
+
'namespace' => $namespace,
|
765 |
+
'routes' => $this->get_route_data( $endpoints ),
|
766 |
+
);
|
767 |
+
$response = rest_ensure_response( $data );
|
768 |
+
|
769 |
+
// Link to the root index
|
770 |
+
$response->add_link( 'up', rest_url( '/' ) );
|
771 |
+
|
772 |
+
/**
|
773 |
+
* Filter the namespace index data.
|
774 |
+
*
|
775 |
+
* This typically is just the route data for the namespace, but you can
|
776 |
+
* add any data you'd like here.
|
777 |
+
*
|
778 |
+
* @param WP_REST_Response $response Response data.
|
779 |
+
* @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter.
|
780 |
+
*/
|
781 |
+
return apply_filters( 'rest_namespace_index', $response, $request );
|
782 |
+
}
|
783 |
+
|
784 |
+
/**
|
785 |
+
* Get the publicly-visible data for routes.
|
786 |
+
*
|
787 |
+
* @param array $routes Routes to get data for
|
788 |
+
* @return array Route data to expose in indexes.
|
789 |
+
*/
|
790 |
+
protected function get_route_data( $routes ) {
|
791 |
+
$available = array();
|
792 |
+
// Find the available routes
|
793 |
+
foreach ( $routes as $route => $callbacks ) {
|
794 |
+
$data = array(
|
795 |
+
'namespace' => '',
|
796 |
+
'methods' => array(),
|
797 |
+
);
|
798 |
+
if ( isset( $this->route_options[ $route ] ) ) {
|
799 |
+
$options = $this->route_options[ $route ];
|
800 |
+
if ( isset( $options['namespace'] ) ) {
|
801 |
+
$data['namespace'] = $options['namespace'];
|
802 |
+
}
|
803 |
+
}
|
804 |
+
|
805 |
+
$route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );
|
806 |
+
|
807 |
+
foreach ( $callbacks as $callback ) {
|
808 |
+
// Skip to the next route if any callback is hidden
|
809 |
+
if ( empty( $callback['show_in_index'] ) ) {
|
810 |
+
continue;
|
811 |
+
}
|
812 |
+
|
813 |
+
$data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) );
|
814 |
+
|
815 |
+
// For non-variable routes, generate links
|
816 |
+
if ( strpos( $route, '{' ) === false ) {
|
817 |
+
$data['_links'] = array(
|
818 |
+
'self' => rest_url( $route ),
|
819 |
+
);
|
820 |
+
}
|
821 |
+
}
|
822 |
+
|
823 |
+
if ( empty( $data['methods'] ) ) {
|
824 |
+
// No methods supported, hide the route
|
825 |
+
continue;
|
826 |
+
}
|
827 |
+
|
828 |
+
$available[ $route ] = apply_filters( 'rest_endpoints_description', $data );
|
829 |
+
}
|
830 |
+
|
831 |
+
/**
|
832 |
+
* Filter the publicly-visible data for routes.
|
833 |
+
*
|
834 |
+
* This data is exposed on indexes and can be used by clients or
|
835 |
+
* developers to investigate the site and find out how to use it. It
|
836 |
+
* acts as a form of self-documentation.
|
837 |
+
*
|
838 |
+
* @param array $available Map of route to route data.
|
839 |
+
* @param array $routes Internal route data as an associative array.
|
840 |
+
*/
|
841 |
+
return apply_filters( 'rest_route_data', $available, $routes );
|
842 |
+
}
|
843 |
+
|
844 |
+
/**
|
845 |
+
* Send a HTTP status code
|
846 |
+
*
|
847 |
+
* @param int $code HTTP status
|
848 |
+
*/
|
849 |
+
protected function set_status( $code ) {
|
850 |
+
status_header( $code );
|
851 |
+
}
|
852 |
+
|
853 |
+
/**
|
854 |
+
* Send a HTTP header
|
855 |
+
*
|
856 |
+
* @param string $key Header key
|
857 |
+
* @param string $value Header value
|
858 |
+
*/
|
859 |
+
public function send_header( $key, $value ) {
|
860 |
+
// Sanitize as per RFC2616 (Section 4.2):
|
861 |
+
// Any LWS that occurs between field-content MAY be replaced with a
|
862 |
+
// single SP before interpreting the field value or forwarding the
|
863 |
+
// message downstream.
|
864 |
+
$value = preg_replace( '/\s+/', ' ', $value );
|
865 |
+
header( sprintf( '%s: %s', $key, $value ) );
|
866 |
+
}
|
867 |
+
|
868 |
+
/**
|
869 |
+
* Send multiple HTTP headers
|
870 |
+
*
|
871 |
+
* @param array Map of header name to header value
|
872 |
+
*/
|
873 |
+
public function send_headers( $headers ) {
|
874 |
+
foreach ( $headers as $key => $value ) {
|
875 |
+
$this->send_header( $key, $value );
|
876 |
+
}
|
877 |
+
}
|
878 |
+
|
879 |
+
/**
|
880 |
+
* Retrieve the raw request entity (body)
|
881 |
+
*
|
882 |
+
* @return string
|
883 |
+
*/
|
884 |
+
public function get_raw_data() {
|
885 |
+
global $HTTP_RAW_POST_DATA;
|
886 |
+
|
887 |
+
// A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,
|
888 |
+
// but we can do it ourself.
|
889 |
+
if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
|
890 |
+
$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
|
891 |
+
}
|
892 |
+
|
893 |
+
return $HTTP_RAW_POST_DATA;
|
894 |
+
}
|
895 |
+
|
896 |
+
/**
|
897 |
+
* Prepares response data to be serialized to JSON
|
898 |
+
*
|
899 |
+
* This supports the JsonSerializable interface for PHP 5.2-5.3 as well.
|
900 |
+
*
|
901 |
+
* @codeCoverageIgnore This is a compatibility shim.
|
902 |
+
*
|
903 |
+
* @param mixed $data Native representation
|
904 |
+
* @return array|string Data ready for `json_encode()`
|
905 |
+
*/
|
906 |
+
public function prepare_response( $data ) {
|
907 |
+
if ( ! defined( 'WP_REST_SERIALIZE_COMPATIBLE' ) || WP_REST_SERIALIZE_COMPATIBLE === false ) {
|
908 |
+
return $data;
|
909 |
+
}
|
910 |
+
|
911 |
+
switch ( gettype( $data ) ) {
|
912 |
+
case 'boolean':
|
913 |
+
case 'integer':
|
914 |
+
case 'double':
|
915 |
+
case 'string':
|
916 |
+
case 'NULL':
|
917 |
+
// These values can be passed through
|
918 |
+
return $data;
|
919 |
+
|
920 |
+
case 'array':
|
921 |
+
// Arrays must be mapped in case they also return objects
|
922 |
+
return array_map( array( $this, 'prepare_response' ), $data );
|
923 |
+
|
924 |
+
case 'object':
|
925 |
+
if ( $data instanceof JsonSerializable ) {
|
926 |
+
$data = $data->jsonSerialize();
|
927 |
+
} else {
|
928 |
+
$data = get_object_vars( $data );
|
929 |
+
}
|
930 |
+
|
931 |
+
// Now, pass the array (or whatever was returned from
|
932 |
+
// jsonSerialize through.)
|
933 |
+
return $this->prepare_response( $data );
|
934 |
+
|
935 |
+
default:
|
936 |
+
return null;
|
937 |
+
}
|
938 |
+
}
|
939 |
+
|
940 |
+
/**
|
941 |
+
* Extract headers from a PHP-style $_SERVER array
|
942 |
+
*
|
943 |
+
* @param array $server Associative array similar to $_SERVER
|
944 |
+
* @return array Headers extracted from the input
|
945 |
+
*/
|
946 |
+
public function get_headers( $server ) {
|
947 |
+
$headers = array();
|
948 |
+
|
949 |
+
// CONTENT_* headers are not prefixed with HTTP_
|
950 |
+
$additional = array( 'CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true );
|
951 |
+
|
952 |
+
foreach ( $server as $key => $value ) {
|
953 |
+
if ( strpos( $key, 'HTTP_' ) === 0 ) {
|
954 |
+
$headers[ substr( $key, 5 ) ] = $value;
|
955 |
+
} elseif ( isset( $additional[ $key ] ) ) {
|
956 |
+
$headers[ $key ] = $value;
|
957 |
+
}
|
958 |
+
}
|
959 |
+
|
960 |
+
return $headers;
|
961 |
+
}
|
962 |
+
}
|
license.txt
ADDED
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 2, June 1991
|
3 |
+
|
4 |
+
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
5 |
+
51 Franklin St, Fifth Floor, Boston, MA 02110, USA
|
6 |
+
|
7 |
+
Everyone is permitted to copy and distribute verbatim copies
|
8 |
+
of this license document, but changing it is not allowed.
|
9 |
+
|
10 |
+
Preamble
|
11 |
+
|
12 |
+
The licenses for most software are designed to take away your
|
13 |
+
freedom to share and change it. By contrast, the GNU General Public
|
14 |
+
License is intended to guarantee your freedom to share and change free
|
15 |
+
software--to make sure the software is free for all its users. This
|
16 |
+
General Public License applies to most of the Free Software
|
17 |
+
Foundation's software and to any other program whose authors commit to
|
18 |
+
using it. (Some other Free Software Foundation software is covered by
|
19 |
+
the GNU Library General Public License instead.) You can apply it to
|
20 |
+
your programs, too.
|
21 |
+
|
22 |
+
When we speak of free software, we are referring to freedom, not
|
23 |
+
price. Our General Public Licenses are designed to make sure that you
|
24 |
+
have the freedom to distribute copies of free software (and charge for
|
25 |
+
this service if you wish), that you receive source code or can get it
|
26 |
+
if you want it, that you can change the software or use pieces of it
|
27 |
+
in new free programs; and that you know you can do these things.
|
28 |
+
|
29 |
+
To protect your rights, we need to make restrictions that forbid
|
30 |
+
anyone to deny you these rights or to ask you to surrender the rights.
|
31 |
+
These restrictions translate to certain responsibilities for you if you
|
32 |
+
distribute copies of the software, or if you modify it.
|
33 |
+
|
34 |
+
For example, if you distribute copies of such a program, whether
|
35 |
+
gratis or for a fee, you must give the recipients all the rights that
|
36 |
+
you have. You must make sure that they, too, receive or can get the
|
37 |
+
source code. And you must show them these terms so they know their
|
38 |
+
rights.
|
39 |
+
|
40 |
+
We protect your rights with two steps: (1) copyright the software, and
|
41 |
+
(2) offer you this license which gives you legal permission to copy,
|
42 |
+
distribute and/or modify the software.
|
43 |
+
|
44 |
+
Also, for each author's protection and ours, we want to make certain
|
45 |
+
that everyone understands that there is no warranty for this free
|
46 |
+
software. If the software is modified by someone else and passed on, we
|
47 |
+
want its recipients to know that what they have is not the original, so
|
48 |
+
that any problems introduced by others will not reflect on the original
|
49 |
+
authors' reputations.
|
50 |
+
|
51 |
+
Finally, any free program is threatened constantly by software
|
52 |
+
patents. We wish to avoid the danger that redistributors of a free
|
53 |
+
program will individually obtain patent licenses, in effect making the
|
54 |
+
program proprietary. To prevent this, we have made it clear that any
|
55 |
+
patent must be licensed for everyone's free use or not licensed at all.
|
56 |
+
|
57 |
+
The precise terms and conditions for copying, distribution and
|
58 |
+
modification follow.
|
59 |
+
|
60 |
+
GNU GENERAL PUBLIC LICENSE
|
61 |
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
62 |
+
|
63 |
+
0. This License applies to any program or other work which contains
|
64 |
+
a notice placed by the copyright holder saying it may be distributed
|
65 |
+
under the terms of this General Public License. The "Program", below,
|
66 |
+
refers to any such program or work, and a "work based on the Program"
|
67 |
+
means either the Program or any derivative work under copyright law:
|
68 |
+
that is to say, a work containing the Program or a portion of it,
|
69 |
+
either verbatim or with modifications and/or translated into another
|
70 |
+
language. (Hereinafter, translation is included without limitation in
|
71 |
+
the term "modification".) Each licensee is addressed as "you".
|
72 |
+
|
73 |
+
Activities other than copying, distribution and modification are not
|
74 |
+
covered by this License; they are outside its scope. The act of
|
75 |
+
running the Program is not restricted, and the output from the Program
|
76 |
+
is covered only if its contents constitute a work based on the
|
77 |
+
Program (independent of having been made by running the Program).
|
78 |
+
Whether that is true depends on what the Program does.
|
79 |
+
|
80 |
+
1. You may copy and distribute verbatim copies of the Program's
|
81 |
+
source code as you receive it, in any medium, provided that you
|
82 |
+
conspicuously and appropriately publish on each copy an appropriate
|
83 |
+
copyright notice and disclaimer of warranty; keep intact all the
|
84 |
+
notices that refer to this License and to the absence of any warranty;
|
85 |
+
and give any other recipients of the Program a copy of this License
|
86 |
+
along with the Program.
|
87 |
+
|
88 |
+
You may charge a fee for the physical act of transferring a copy, and
|
89 |
+
you may at your option offer warranty protection in exchange for a fee.
|
90 |
+
|
91 |
+
2. You may modify your copy or copies of the Program or any portion
|
92 |
+
of it, thus forming a work based on the Program, and copy and
|
93 |
+
distribute such modifications or work under the terms of Section 1
|
94 |
+
above, provided that you also meet all of these conditions:
|
95 |
+
|
96 |
+
a) You must cause the modified files to carry prominent notices
|
97 |
+
stating that you changed the files and the date of any change.
|
98 |
+
|
99 |
+
b) You must cause any work that you distribute or publish, that in
|
100 |
+
whole or in part contains or is derived from the Program or any
|
101 |
+
part thereof, to be licensed as a whole at no charge to all third
|
102 |
+
parties under the terms of this License.
|
103 |
+
|
104 |
+
c) If the modified program normally reads commands interactively
|
105 |
+
when run, you must cause it, when started running for such
|
106 |
+
interactive use in the most ordinary way, to print or display an
|
107 |
+
announcement including an appropriate copyright notice and a
|
108 |
+
notice that there is no warranty (or else, saying that you provide
|
109 |
+
a warranty) and that users may redistribute the program under
|
110 |
+
these conditions, and telling the user how to view a copy of this
|
111 |
+
License. (Exception: if the Program itself is interactive but
|
112 |
+
does not normally print such an announcement, your work based on
|
113 |
+
the Program is not required to print an announcement.)
|
114 |
+
|
115 |
+
These requirements apply to the modified work as a whole. If
|
116 |
+
identifiable sections of that work are not derived from the Program,
|
117 |
+
and can be reasonably considered independent and separate works in
|
118 |
+
themselves, then this License, and its terms, do not apply to those
|
119 |
+
sections when you distribute them as separate works. But when you
|
120 |
+
distribute the same sections as part of a whole which is a work based
|
121 |
+
on the Program, the distribution of the whole must be on the terms of
|
122 |
+
this License, whose permissions for other licensees extend to the
|
123 |
+
entire whole, and thus to each and every part regardless of who wrote it.
|
124 |
+
Thus, it is not the intent of this section to claim rights or contest
|
125 |
+
your rights to work written entirely by you; rather, the intent is to
|
126 |
+
exercise the right to control the distribution of derivative or
|
127 |
+
collective works based on the Program.
|
128 |
+
|
129 |
+
In addition, mere aggregation of another work not based on the Program
|
130 |
+
with the Program (or with a work based on the Program) on a volume of
|
131 |
+
a storage or distribution medium does not bring the other work under
|
132 |
+
the scope of this License.
|
133 |
+
|
134 |
+
3. You may copy and distribute the Program (or a work based on it,
|
135 |
+
under Section 2) in object code or executable form under the terms of
|
136 |
+
Sections 1 and 2 above provided that you also do one of the following:
|
137 |
+
|
138 |
+
a) Accompany it with the complete corresponding machine-readable
|
139 |
+
source code, which must be distributed under the terms of Sections
|
140 |
+
1 and 2 above on a medium customarily used for software interchange; or,
|
141 |
+
|
142 |
+
b) Accompany it with a written offer, valid for at least three
|
143 |
+
years, to give any third party, for a charge no more than your
|
144 |
+
cost of physically performing source distribution, a complete
|
145 |
+
machine-readable copy of the corresponding source code, to be
|
146 |
+
distributed under the terms of Sections 1 and 2 above on a medium
|
147 |
+
customarily used for software interchange; or,
|
148 |
+
|
149 |
+
c) Accompany it with the information you received as to the offer
|
150 |
+
to distribute corresponding source code. (This alternative is
|
151 |
+
allowed only for noncommercial distribution and only if you
|
152 |
+
received the program in object code or executable form with such
|
153 |
+
an offer, in accord with Subsection b above.)
|
154 |
+
|
155 |
+
The source code for a work means the preferred form of the work for
|
156 |
+
making modifications to it. For an executable work, complete source
|
157 |
+
code means all the source code for all modules it contains, plus any
|
158 |
+
associated interface definition files, plus the scripts used to
|
159 |
+
control compilation and installation of the executable. However, as a
|
160 |
+
special exception, the source code distributed need not include
|
161 |
+
anything that is normally distributed (in either source or binary
|
162 |
+
form) with the major components (compiler, kernel, and so on) of the
|
163 |
+
operating system on which the executable runs, unless that component
|
164 |
+
itself accompanies the executable.
|
165 |
+
|
166 |
+
If distribution of executable or object code is made by offering
|
167 |
+
access to copy from a designated place, then offering equivalent
|
168 |
+
access to copy the source code from the same place counts as
|
169 |
+
distribution of the source code, even though third parties are not
|
170 |
+
compelled to copy the source along with the object code.
|
171 |
+
|
172 |
+
4. You may not copy, modify, sublicense, or distribute the Program
|
173 |
+
except as expressly provided under this License. Any attempt
|
174 |
+
otherwise to copy, modify, sublicense or distribute the Program is
|
175 |
+
void, and will automatically terminate your rights under this License.
|
176 |
+
However, parties who have received copies, or rights, from you under
|
177 |
+
this License will not have their licenses terminated so long as such
|
178 |
+
parties remain in full compliance.
|
179 |
+
|
180 |
+
5. You are not required to accept this License, since you have not
|
181 |
+
signed it. However, nothing else grants you permission to modify or
|
182 |
+
distribute the Program or its derivative works. These actions are
|
183 |
+
prohibited by law if you do not accept this License. Therefore, by
|
184 |
+
modifying or distributing the Program (or any work based on the
|
185 |
+
Program), you indicate your acceptance of this License to do so, and
|
186 |
+
all its terms and conditions for copying, distributing or modifying
|
187 |
+
the Program or works based on it.
|
188 |
+
|
189 |
+
6. Each time you redistribute the Program (or any work based on the
|
190 |
+
Program), the recipient automatically receives a license from the
|
191 |
+
original licensor to copy, distribute or modify the Program subject to
|
192 |
+
these terms and conditions. You may not impose any further
|
193 |
+
restrictions on the recipients' exercise of the rights granted herein.
|
194 |
+
You are not responsible for enforcing compliance by third parties to
|
195 |
+
this License.
|
196 |
+
|
197 |
+
7. If, as a consequence of a court judgment or allegation of patent
|
198 |
+
infringement or for any other reason (not limited to patent issues),
|
199 |
+
conditions are imposed on you (whether by court order, agreement or
|
200 |
+
otherwise) that contradict the conditions of this License, they do not
|
201 |
+
excuse you from the conditions of this License. If you cannot
|
202 |
+
distribute so as to satisfy simultaneously your obligations under this
|
203 |
+
License and any other pertinent obligations, then as a consequence you
|
204 |
+
may not distribute the Program at all. For example, if a patent
|
205 |
+
license would not permit royalty-free redistribution of the Program by
|
206 |
+
all those who receive copies directly or indirectly through you, then
|
207 |
+
the only way you could satisfy both it and this License would be to
|
208 |
+
refrain entirely from distribution of the Program.
|
209 |
+
|
210 |
+
If any portion of this section is held invalid or unenforceable under
|
211 |
+
any particular circumstance, the balance of the section is intended to
|
212 |
+
apply and the section as a whole is intended to apply in other
|
213 |
+
circumstances.
|
214 |
+
|
215 |
+
It is not the purpose of this section to induce you to infringe any
|
216 |
+
patents or other property right claims or to contest validity of any
|
217 |
+
such claims; this section has the sole purpose of protecting the
|
218 |
+
integrity of the free software distribution system, which is
|
219 |
+
implemented by public license practices. Many people have made
|
220 |
+
generous contributions to the wide range of software distributed
|
221 |
+
through that system in reliance on consistent application of that
|
222 |
+
system; it is up to the author/donor to decide if he or she is willing
|
223 |
+
to distribute software through any other system and a licensee cannot
|
224 |
+
impose that choice.
|
225 |
+
|
226 |
+
This section is intended to make thoroughly clear what is believed to
|
227 |
+
be a consequence of the rest of this License.
|
228 |
+
|
229 |
+
8. If the distribution and/or use of the Program is restricted in
|
230 |
+
certain countries either by patents or by copyrighted interfaces, the
|
231 |
+
original copyright holder who places the Program under this License
|
232 |
+
may add an explicit geographical distribution limitation excluding
|
233 |
+
those countries, so that distribution is permitted only in or among
|
234 |
+
countries not thus excluded. In such case, this License incorporates
|
235 |
+
the limitation as if written in the body of this License.
|
236 |
+
|
237 |
+
9. The Free Software Foundation may publish revised and/or new versions
|
238 |
+
of the General Public License from time to time. Such new versions will
|
239 |
+
be similar in spirit to the present version, but may differ in detail to
|
240 |
+
address new problems or concerns.
|
241 |
+
|
242 |
+
Each version is given a distinguishing version number. If the Program
|
243 |
+
specifies a version number of this License which applies to it and "any
|
244 |
+
later version", you have the option of following the terms and conditions
|
245 |
+
either of that version or of any later version published by the Free
|
246 |
+
Software Foundation. If the Program does not specify a version number of
|
247 |
+
this License, you may choose any version ever published by the Free Software
|
248 |
+
Foundation.
|
249 |
+
|
250 |
+
10. If you wish to incorporate parts of the Program into other free
|
251 |
+
programs whose distribution conditions are different, write to the author
|
252 |
+
to ask for permission. For software which is copyrighted by the Free
|
253 |
+
Software Foundation, write to the Free Software Foundation; we sometimes
|
254 |
+
make exceptions for this. Our decision will be guided by the two goals
|
255 |
+
of preserving the free status of all derivatives of our free software and
|
256 |
+
of promoting the sharing and reuse of software generally.
|
257 |
+
|
258 |
+
NO WARRANTY
|
259 |
+
|
260 |
+
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
261 |
+
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
262 |
+
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
263 |
+
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
264 |
+
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
265 |
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
266 |
+
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
267 |
+
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
268 |
+
REPAIR OR CORRECTION.
|
269 |
+
|
270 |
+
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
271 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
272 |
+
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
273 |
+
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
274 |
+
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
275 |
+
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
276 |
+
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
277 |
+
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
278 |
+
POSSIBILITY OF SUCH DAMAGES.
|
279 |
+
|
280 |
+
END OF TERMS AND CONDITIONS
|
281 |
+
|
plugin.php
ADDED
@@ -0,0 +1,707 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin Name: WP REST API
|
4 |
+
* Description: JSON-based REST API for WordPress, developed as part of GSoC 2013.
|
5 |
+
* Author: WP REST API Team
|
6 |
+
* Author URI: http://wp-api.org
|
7 |
+
* Version: 2.0-beta3
|
8 |
+
* Plugin URI: https://github.com/WP-API/WP-API
|
9 |
+
* License: GPL2+
|
10 |
+
*/
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Version number for our API.
|
14 |
+
*
|
15 |
+
* @var string
|
16 |
+
*/
|
17 |
+
define( 'REST_API_VERSION', '2.0-beta3' );
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Include our files for the API.
|
21 |
+
*/
|
22 |
+
include_once( dirname( __FILE__ ) . '/compatibility-v1.php' );
|
23 |
+
include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-jsonserializable.php' );
|
24 |
+
|
25 |
+
include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-rest-server.php' );
|
26 |
+
|
27 |
+
include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-http-responseinterface.php' );
|
28 |
+
include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-http-response.php' );
|
29 |
+
include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-rest-response.php' );
|
30 |
+
require_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-rest-request.php' );
|
31 |
+
|
32 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-controller.php';
|
33 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-controller.php';
|
34 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-attachments-controller.php';
|
35 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-types-controller.php';
|
36 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-statuses-controller.php';
|
37 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-revisions-controller.php';
|
38 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-taxonomies-controller.php';
|
39 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-terms-controller.php';
|
40 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-users-controller.php';
|
41 |
+
require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
|
42 |
+
include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-controller.php';
|
43 |
+
include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-posts-controller.php';
|
44 |
+
include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-terms-controller.php';
|
45 |
+
|
46 |
+
include_once( dirname( __FILE__ ) . '/extras.php' );
|
47 |
+
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Register a REST API route
|
51 |
+
*
|
52 |
+
* @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
|
53 |
+
* @param string $route The base URL for route you are adding.
|
54 |
+
* @param array $args Either an array of options for the endpoint, or an array of arrays for multiple methods
|
55 |
+
* @param boolean $override If the route already exists, should we override it? True overrides, false merges (with newer overriding if duplicate keys exist)
|
56 |
+
*/
|
57 |
+
function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
|
58 |
+
|
59 |
+
/** @var WP_REST_Server $wp_rest_server */
|
60 |
+
global $wp_rest_server;
|
61 |
+
|
62 |
+
if ( isset( $args['callback'] ) ) {
|
63 |
+
// Upgrade a single set to multiple
|
64 |
+
$args = array( $args );
|
65 |
+
}
|
66 |
+
|
67 |
+
$defaults = array(
|
68 |
+
'methods' => 'GET',
|
69 |
+
'callback' => null,
|
70 |
+
'args' => array(),
|
71 |
+
);
|
72 |
+
foreach ( $args as &$arg_group ) {
|
73 |
+
$arg_group = array_merge( $defaults, $arg_group );
|
74 |
+
}
|
75 |
+
|
76 |
+
$full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
|
77 |
+
$wp_rest_server->register_route( $namespace, $full_route, $args, $override );
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Register a new field on an existing WordPress object type
|
82 |
+
*
|
83 |
+
* @param string|array $object_type "post"|"term"|"comment" etc
|
84 |
+
* @param string $attribute The attribute name
|
85 |
+
* @param array $args
|
86 |
+
* @return bool|wp_error
|
87 |
+
*/
|
88 |
+
function register_api_field( $object_type, $attribute, $args = array() ) {
|
89 |
+
|
90 |
+
$defaults = array(
|
91 |
+
'get_callback' => null,
|
92 |
+
'update_callback' => null,
|
93 |
+
'schema' => null,
|
94 |
+
);
|
95 |
+
|
96 |
+
$args = wp_parse_args( $args, $defaults );
|
97 |
+
|
98 |
+
global $wp_rest_additional_fields;
|
99 |
+
|
100 |
+
$object_types = (array) $object_type;
|
101 |
+
|
102 |
+
foreach ( $object_types as $object_type ) {
|
103 |
+
$wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
|
104 |
+
}
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Add the extra Post Type registration arguments we need
|
109 |
+
* These attributes will eventually be committed to core.
|
110 |
+
*/
|
111 |
+
function _add_extra_api_post_type_arguments() {
|
112 |
+
global $wp_post_types;
|
113 |
+
|
114 |
+
$wp_post_types['post']->show_in_rest = true;
|
115 |
+
$wp_post_types['post']->rest_base = 'posts';
|
116 |
+
$wp_post_types['post']->rest_controller_class = 'WP_REST_Posts_Controller';
|
117 |
+
|
118 |
+
$wp_post_types['page']->show_in_rest = true;
|
119 |
+
$wp_post_types['page']->rest_base = 'pages';
|
120 |
+
$wp_post_types['page']->rest_controller_class = 'WP_REST_Posts_Controller';
|
121 |
+
|
122 |
+
$wp_post_types['attachment']->show_in_rest = true;
|
123 |
+
$wp_post_types['attachment']->rest_base = 'media';
|
124 |
+
$wp_post_types['attachment']->rest_controller_class = 'WP_REST_Attachments_Controller';
|
125 |
+
|
126 |
+
}
|
127 |
+
add_action( 'init', '_add_extra_api_post_type_arguments', 11 );
|
128 |
+
|
129 |
+
/**
|
130 |
+
* Add the extra Taxonomy registration arguments we need.
|
131 |
+
* These attributes will eventually be committed to core.
|
132 |
+
*/
|
133 |
+
function _add_extra_api_taxonomy_arguments() {
|
134 |
+
global $wp_taxonomies;
|
135 |
+
|
136 |
+
$wp_taxonomies['category']->show_in_rest = true;
|
137 |
+
$wp_taxonomies['category']->rest_base = 'category';
|
138 |
+
$wp_taxonomies['category']->rest_controller_class = 'WP_REST_Terms_Controller';
|
139 |
+
|
140 |
+
$wp_taxonomies['post_tag']->show_in_rest = true;
|
141 |
+
$wp_taxonomies['post_tag']->rest_base = 'tag';
|
142 |
+
$wp_taxonomies['post_tag']->rest_controller_class = 'WP_REST_Terms_Controller';
|
143 |
+
}
|
144 |
+
add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Register default REST API routes
|
148 |
+
*/
|
149 |
+
function create_initial_rest_routes() {
|
150 |
+
|
151 |
+
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
|
152 |
+
$class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller';
|
153 |
+
|
154 |
+
if ( ! class_exists( $class ) ) {
|
155 |
+
continue;
|
156 |
+
}
|
157 |
+
$controller = new $class( $post_type->name );
|
158 |
+
if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
|
159 |
+
continue;
|
160 |
+
}
|
161 |
+
|
162 |
+
$controller->register_routes();
|
163 |
+
|
164 |
+
if ( post_type_supports( $post_type->name, 'custom-fields' ) ) {
|
165 |
+
$meta_controller = new WP_REST_Meta_Posts_Controller( $post_type->name );
|
166 |
+
$meta_controller->register_routes();
|
167 |
+
}
|
168 |
+
if ( post_type_supports( $post_type->name, 'revisions' ) ) {
|
169 |
+
$revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
|
170 |
+
$revisions_controller->register_routes();
|
171 |
+
}
|
172 |
+
|
173 |
+
foreach ( get_object_taxonomies( $post_type->name, 'objects' ) as $taxonomy ) {
|
174 |
+
|
175 |
+
if ( empty( $taxonomy->show_in_rest ) ) {
|
176 |
+
continue;
|
177 |
+
}
|
178 |
+
|
179 |
+
$posts_terms_controller = new WP_REST_Posts_Terms_Controller( $post_type->name, $taxonomy->name );
|
180 |
+
$posts_terms_controller->register_routes();
|
181 |
+
}
|
182 |
+
}
|
183 |
+
|
184 |
+
/*
|
185 |
+
* Post types
|
186 |
+
*/
|
187 |
+
$controller = new WP_REST_Post_Types_Controller;
|
188 |
+
$controller->register_routes();
|
189 |
+
|
190 |
+
/*
|
191 |
+
* Post statuses
|
192 |
+
*/
|
193 |
+
$controller = new WP_REST_Post_Statuses_Controller;
|
194 |
+
$controller->register_routes();
|
195 |
+
|
196 |
+
/*
|
197 |
+
* Taxonomies
|
198 |
+
*/
|
199 |
+
$controller = new WP_REST_Taxonomies_Controller;
|
200 |
+
$controller->register_routes();
|
201 |
+
|
202 |
+
/*
|
203 |
+
* Terms
|
204 |
+
*/
|
205 |
+
foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
|
206 |
+
$class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
|
207 |
+
|
208 |
+
if ( ! class_exists( $class ) ) {
|
209 |
+
continue;
|
210 |
+
}
|
211 |
+
$controller = new $class( $taxonomy->name );
|
212 |
+
if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
|
213 |
+
continue;
|
214 |
+
}
|
215 |
+
|
216 |
+
$controller->register_routes();
|
217 |
+
}
|
218 |
+
|
219 |
+
/*
|
220 |
+
* Users
|
221 |
+
*/
|
222 |
+
$controller = new WP_REST_Users_Controller;
|
223 |
+
$controller->register_routes();
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Comments
|
227 |
+
*/
|
228 |
+
$controller = new WP_REST_Comments_Controller;
|
229 |
+
$controller->register_routes();
|
230 |
+
|
231 |
+
}
|
232 |
+
add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
|
233 |
+
|
234 |
+
/**
|
235 |
+
* Register rewrite rules for the API.
|
236 |
+
*
|
237 |
+
* @global WP $wp Current WordPress environment instance.
|
238 |
+
*/
|
239 |
+
function rest_api_init() {
|
240 |
+
rest_api_register_rewrites();
|
241 |
+
|
242 |
+
global $wp;
|
243 |
+
$wp->add_query_var( 'rest_route' );
|
244 |
+
}
|
245 |
+
add_action( 'init', 'rest_api_init' );
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Add rewrite rules.
|
249 |
+
*/
|
250 |
+
function rest_api_register_rewrites() {
|
251 |
+
add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
|
252 |
+
add_rewrite_rule( '^' . rest_get_url_prefix() . '(.*)?','index.php?rest_route=$matches[1]','top' );
|
253 |
+
}
|
254 |
+
|
255 |
+
/**
|
256 |
+
* Determine if the rewrite rules should be flushed.
|
257 |
+
*/
|
258 |
+
function rest_api_maybe_flush_rewrites() {
|
259 |
+
$version = get_option( 'rest_api_plugin_version', null );
|
260 |
+
|
261 |
+
if ( empty( $version ) || REST_API_VERSION !== $version ) {
|
262 |
+
flush_rewrite_rules();
|
263 |
+
update_option( 'rest_api_plugin_version', REST_API_VERSION );
|
264 |
+
}
|
265 |
+
|
266 |
+
}
|
267 |
+
add_action( 'init', 'rest_api_maybe_flush_rewrites', 999 );
|
268 |
+
|
269 |
+
/**
|
270 |
+
* Register the default REST API filters.
|
271 |
+
*
|
272 |
+
* @internal This will live in default-filters.php
|
273 |
+
*
|
274 |
+
* @global WP_REST_Posts $WP_REST_posts
|
275 |
+
* @global WP_REST_Pages $WP_REST_pages
|
276 |
+
* @global WP_REST_Media $WP_REST_media
|
277 |
+
* @global WP_REST_Taxonomies $WP_REST_taxonomies
|
278 |
+
*
|
279 |
+
* @param WP_REST_Server $server Server object.
|
280 |
+
*/
|
281 |
+
function rest_api_default_filters( $server ) {
|
282 |
+
// Deprecated reporting.
|
283 |
+
add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
|
284 |
+
add_filter( 'deprecated_function_trigger_error', '__return_false' );
|
285 |
+
add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
|
286 |
+
add_filter( 'deprecated_argument_trigger_error', '__return_false' );
|
287 |
+
|
288 |
+
// Default serving
|
289 |
+
add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
|
290 |
+
add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
|
291 |
+
|
292 |
+
add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
|
293 |
+
|
294 |
+
}
|
295 |
+
add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 );
|
296 |
+
|
297 |
+
/**
|
298 |
+
* Load the REST API.
|
299 |
+
*
|
300 |
+
* @todo Extract code that should be unit tested into isolated methods such as
|
301 |
+
* the wp_rest_server_class filter and serving requests. This would also
|
302 |
+
* help for code re-use by `wp-json` endpoint. Note that we can't unit
|
303 |
+
* test any method that calls die().
|
304 |
+
*/
|
305 |
+
function rest_api_loaded() {
|
306 |
+
if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
|
307 |
+
return;
|
308 |
+
}
|
309 |
+
|
310 |
+
/**
|
311 |
+
* Whether this is a XML-RPC Request.
|
312 |
+
*
|
313 |
+
* @var bool
|
314 |
+
* @todo Remove me in favour of REST_REQUEST
|
315 |
+
*/
|
316 |
+
define( 'XMLRPC_REQUEST', true );
|
317 |
+
|
318 |
+
/**
|
319 |
+
* Whether this is a REST Request.
|
320 |
+
*
|
321 |
+
* @var bool
|
322 |
+
*/
|
323 |
+
define( 'REST_REQUEST', true );
|
324 |
+
|
325 |
+
/** @var WP_REST_Server $wp_rest_server */
|
326 |
+
global $wp_rest_server;
|
327 |
+
|
328 |
+
// Allow for a plugin to insert a different class to handle requests.
|
329 |
+
$wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
|
330 |
+
$wp_rest_server = new $wp_rest_server_class;
|
331 |
+
|
332 |
+
/**
|
333 |
+
* Fires when preparing to serve an API request.
|
334 |
+
*
|
335 |
+
* Endpoint objects should be created and register their hooks on this
|
336 |
+
* action rather than another action to ensure they're only loaded when
|
337 |
+
* needed.
|
338 |
+
*
|
339 |
+
* @param WP_REST_Server $wp_rest_server Server object.
|
340 |
+
*/
|
341 |
+
do_action( 'rest_api_init', $wp_rest_server );
|
342 |
+
|
343 |
+
// Fire off the request.
|
344 |
+
$wp_rest_server->serve_request( $GLOBALS['wp']->query_vars['rest_route'] );
|
345 |
+
|
346 |
+
// We're done.
|
347 |
+
die();
|
348 |
+
}
|
349 |
+
add_action( 'parse_request', 'rest_api_loaded' );
|
350 |
+
|
351 |
+
/**
|
352 |
+
* Register routes and flush the rewrite rules on activation.
|
353 |
+
*
|
354 |
+
* @param bool $network_wide ?
|
355 |
+
*/
|
356 |
+
function rest_api_activation( $network_wide ) {
|
357 |
+
if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) {
|
358 |
+
$mu_blogs = wp_get_sites();
|
359 |
+
|
360 |
+
foreach ( $mu_blogs as $mu_blog ) {
|
361 |
+
switch_to_blog( $mu_blog['blog_id'] );
|
362 |
+
|
363 |
+
rest_api_register_rewrites();
|
364 |
+
update_option( 'rest_api_plugin_version', null );
|
365 |
+
}
|
366 |
+
|
367 |
+
restore_current_blog();
|
368 |
+
} else {
|
369 |
+
rest_api_register_rewrites();
|
370 |
+
update_option( 'rest_api_plugin_version', null );
|
371 |
+
}
|
372 |
+
}
|
373 |
+
register_activation_hook( __FILE__, 'rest_api_activation' );
|
374 |
+
|
375 |
+
/**
|
376 |
+
* Flush the rewrite rules on deactivation.
|
377 |
+
*
|
378 |
+
* @param bool $network_wide ?
|
379 |
+
*/
|
380 |
+
function rest_api_deactivation( $network_wide ) {
|
381 |
+
if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) {
|
382 |
+
|
383 |
+
$mu_blogs = wp_get_sites();
|
384 |
+
|
385 |
+
foreach ( $mu_blogs as $mu_blog ) {
|
386 |
+
switch_to_blog( $mu_blog['blog_id'] );
|
387 |
+
delete_option( 'rest_api_plugin_version' );
|
388 |
+
}
|
389 |
+
|
390 |
+
restore_current_blog();
|
391 |
+
} else {
|
392 |
+
delete_option( 'rest_api_plugin_version' );
|
393 |
+
}
|
394 |
+
}
|
395 |
+
register_deactivation_hook( __FILE__, 'rest_api_deactivation' );
|
396 |
+
|
397 |
+
/**
|
398 |
+
* Get the URL prefix for any API resource.
|
399 |
+
*
|
400 |
+
* @return string Prefix.
|
401 |
+
*/
|
402 |
+
function rest_get_url_prefix() {
|
403 |
+
/**
|
404 |
+
* Filter the rest URL prefix.
|
405 |
+
*
|
406 |
+
* @since 1.0
|
407 |
+
*
|
408 |
+
* @param string $prefix URL prefix. Default 'wp-json'.
|
409 |
+
*/
|
410 |
+
return apply_filters( 'rest_url_prefix', 'wp-json' );
|
411 |
+
}
|
412 |
+
|
413 |
+
/**
|
414 |
+
* Get URL to a REST endpoint on a site.
|
415 |
+
*
|
416 |
+
* @todo Check if this is even necessary
|
417 |
+
*
|
418 |
+
* @param int $blog_id Blog ID. Optional. The ID of the multisite blog to get URL for. Default null of null returns URL for current blog.
|
419 |
+
* @param string $path Optional. REST route. Default empty.
|
420 |
+
* @param string $scheme Optional. Sanitization scheme. Default 'json'.
|
421 |
+
* @return string Full URL to the endpoint.
|
422 |
+
*/
|
423 |
+
function get_rest_url( $blog_id = null, $path = '', $scheme = 'json' ) {
|
424 |
+
if ( get_option( 'permalink_structure' ) ) {
|
425 |
+
$url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
|
426 |
+
|
427 |
+
if ( ! empty( $path ) && is_string( $path ) && strpos( $path, '..' ) === false ) {
|
428 |
+
$url .= '/' . ltrim( $path, '/' );
|
429 |
+
}
|
430 |
+
} else {
|
431 |
+
$url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
|
432 |
+
|
433 |
+
if ( empty( $path ) ) {
|
434 |
+
$path = '/';
|
435 |
+
} else {
|
436 |
+
$path = '/' . ltrim( $path, '/' );
|
437 |
+
}
|
438 |
+
|
439 |
+
$url = add_query_arg( 'rest_route', $path, $url );
|
440 |
+
}
|
441 |
+
|
442 |
+
/**
|
443 |
+
* Filter the REST URL.
|
444 |
+
*
|
445 |
+
* @since 1.0
|
446 |
+
*
|
447 |
+
* @param string $url REST URL.
|
448 |
+
* @param string $path REST route.
|
449 |
+
* @param int $blod_ig Blog ID.
|
450 |
+
* @param string $scheme Sanitization scheme.
|
451 |
+
*/
|
452 |
+
return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
|
453 |
+
}
|
454 |
+
|
455 |
+
/**
|
456 |
+
* Get URL to a REST endpoint.
|
457 |
+
*
|
458 |
+
* @param string $path Optional. REST route. Default empty.
|
459 |
+
* @param string $scheme Optional. Sanitization scheme. Default 'json'.
|
460 |
+
* @return string Full URL to the endpoint.
|
461 |
+
*/
|
462 |
+
function rest_url( $path = '', $scheme = 'json' ) {
|
463 |
+
return get_rest_url( null, $path, $scheme );
|
464 |
+
}
|
465 |
+
|
466 |
+
/**
|
467 |
+
* Do a REST request.
|
468 |
+
* Used primarily to route internal requests through WP_REST_Server
|
469 |
+
*
|
470 |
+
* @param WP_REST_Request|string $request
|
471 |
+
* @return WP_REST_Response
|
472 |
+
*/
|
473 |
+
function rest_do_request( $request ) {
|
474 |
+
global $wp_rest_server;
|
475 |
+
$request = rest_ensure_request( $request );
|
476 |
+
return $wp_rest_server->dispatch( $request );
|
477 |
+
}
|
478 |
+
|
479 |
+
/**
|
480 |
+
* Ensure request arguments are a request object.
|
481 |
+
*
|
482 |
+
* This ensures that the request is consistent.
|
483 |
+
*
|
484 |
+
* @param array|WP_REST_Request $request Request to check.
|
485 |
+
* @return WP_REST_Request
|
486 |
+
*/
|
487 |
+
function rest_ensure_request( $request ) {
|
488 |
+
if ( $request instanceof WP_REST_Request ) {
|
489 |
+
return $request;
|
490 |
+
}
|
491 |
+
|
492 |
+
return new WP_REST_Request( 'GET', '', $request );
|
493 |
+
}
|
494 |
+
|
495 |
+
/**
|
496 |
+
* Ensure a REST response is a response object.
|
497 |
+
*
|
498 |
+
* This ensures that the response is consistent, and implements
|
499 |
+
* {@see WP_HTTP_ResponseInterface}, allowing usage of
|
500 |
+
* `set_status`/`header`/etc without needing to double-check the object. Will
|
501 |
+
* also allow {@see WP_Error} to indicate error responses, so users should
|
502 |
+
* immediately check for this value.
|
503 |
+
*
|
504 |
+
* @param WP_Error|WP_HTTP_ResponseInterface|mixed $response Response to check.
|
505 |
+
* @return mixed WP_Error if present, WP_HTTP_ResponseInterface if instance,
|
506 |
+
* otherwise WP_REST_Response.
|
507 |
+
*/
|
508 |
+
function rest_ensure_response( $response ) {
|
509 |
+
if ( is_wp_error( $response ) ) {
|
510 |
+
return $response;
|
511 |
+
}
|
512 |
+
|
513 |
+
if ( $response instanceof WP_HTTP_ResponseInterface ) {
|
514 |
+
return $response;
|
515 |
+
}
|
516 |
+
|
517 |
+
return new WP_REST_Response( $response );
|
518 |
+
}
|
519 |
+
|
520 |
+
/**
|
521 |
+
* Handle {@see _deprecated_function()} errors.
|
522 |
+
*
|
523 |
+
* @param string $function Function name.
|
524 |
+
* @param string $replacement Replacement function name.
|
525 |
+
* @param string $version Version.
|
526 |
+
*/
|
527 |
+
function rest_handle_deprecated_function( $function, $replacement, $version ) {
|
528 |
+
if ( ! empty( $replacement ) ) {
|
529 |
+
$string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
|
530 |
+
} else {
|
531 |
+
$string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
|
532 |
+
}
|
533 |
+
|
534 |
+
header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
|
535 |
+
}
|
536 |
+
|
537 |
+
/**
|
538 |
+
* Handle {@see _deprecated_function} errors.
|
539 |
+
*
|
540 |
+
* @param string $function Function name.
|
541 |
+
* @param string $replacement Replacement function name.
|
542 |
+
* @param string $version Version.
|
543 |
+
*/
|
544 |
+
function rest_handle_deprecated_argument( $function, $replacement, $version ) {
|
545 |
+
if ( ! empty( $replacement ) ) {
|
546 |
+
$string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $replacement );
|
547 |
+
} else {
|
548 |
+
$string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
|
549 |
+
}
|
550 |
+
|
551 |
+
header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
|
552 |
+
}
|
553 |
+
|
554 |
+
/**
|
555 |
+
* Send Cross-Origin Resource Sharing headers with API requests
|
556 |
+
*
|
557 |
+
* @param mixed $value Response data
|
558 |
+
* @return mixed Response data
|
559 |
+
*/
|
560 |
+
function rest_send_cors_headers( $value ) {
|
561 |
+
$origin = get_http_origin();
|
562 |
+
|
563 |
+
if ( $origin ) {
|
564 |
+
header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
|
565 |
+
header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
|
566 |
+
header( 'Access-Control-Allow-Credentials: true' );
|
567 |
+
}
|
568 |
+
|
569 |
+
return $value;
|
570 |
+
}
|
571 |
+
|
572 |
+
/**
|
573 |
+
* Handle OPTIONS requests for the server
|
574 |
+
*
|
575 |
+
* This is handled outside of the server code, as it doesn't obey normal route
|
576 |
+
* mapping.
|
577 |
+
*
|
578 |
+
* @param mixed $response Current response, either response or `null` to indicate pass-through.
|
579 |
+
* @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
|
580 |
+
* @param WP_REST_Request $request The request that was used to make current response.
|
581 |
+
* @return WP_REST_Response $response Modified response, either response or `null` to indicate pass-through.
|
582 |
+
*/
|
583 |
+
function rest_handle_options_request( $response, $handler, $request ) {
|
584 |
+
if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
|
585 |
+
return $response;
|
586 |
+
}
|
587 |
+
|
588 |
+
$response = new WP_REST_Response();
|
589 |
+
|
590 |
+
$accept = array();
|
591 |
+
|
592 |
+
foreach ( $handler->get_routes() as $route => $endpoints ) {
|
593 |
+
$match = preg_match( '@^' . $route . '$@i', $request->get_route(), $args );
|
594 |
+
|
595 |
+
if ( ! $match ) {
|
596 |
+
continue;
|
597 |
+
}
|
598 |
+
|
599 |
+
foreach ( $endpoints as $endpoint ) {
|
600 |
+
$accept = array_merge( $accept, $endpoint['methods'] );
|
601 |
+
}
|
602 |
+
break;
|
603 |
+
}
|
604 |
+
$accept = array_keys( $accept );
|
605 |
+
|
606 |
+
$response->header( 'Accept', implode( ', ', $accept ) );
|
607 |
+
|
608 |
+
return $response;
|
609 |
+
}
|
610 |
+
|
611 |
+
/**
|
612 |
+
* Send the "Allow" header to state all methods that can be sen
|
613 |
+
* to the current route
|
614 |
+
*
|
615 |
+
* @param WP_REST_Response $response Current response being served.
|
616 |
+
* @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server)
|
617 |
+
* @param WP_REST_Request $request The request that was used to make current response.
|
618 |
+
*/
|
619 |
+
function rest_send_allow_header( $response, $server, $request ) {
|
620 |
+
|
621 |
+
$matched_route = $response->get_matched_route();
|
622 |
+
|
623 |
+
if ( ! $matched_route ) {
|
624 |
+
return $response;
|
625 |
+
}
|
626 |
+
|
627 |
+
$routes = $server->get_routes();
|
628 |
+
|
629 |
+
$allowed_methods = array();
|
630 |
+
|
631 |
+
// get the allowed methods across the routes
|
632 |
+
foreach ( $routes[ $matched_route ] as $_handler ) {
|
633 |
+
foreach ( $_handler['methods'] as $handler_method => $value ) {
|
634 |
+
|
635 |
+
if ( ! empty( $_handler['permission_callback'] ) ) {
|
636 |
+
|
637 |
+
$permission = call_user_func( $_handler['permission_callback'], $request );
|
638 |
+
|
639 |
+
$allowed_methods[ $handler_method ] = true === $permission;
|
640 |
+
} else {
|
641 |
+
$allowed_methods[ $handler_method ] = true;
|
642 |
+
}
|
643 |
+
}
|
644 |
+
}
|
645 |
+
|
646 |
+
// strip out all the methods that are not allowed (false values)
|
647 |
+
$allowed_methods = array_filter( $allowed_methods );
|
648 |
+
|
649 |
+
if ( $allowed_methods ) {
|
650 |
+
$response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
|
651 |
+
}
|
652 |
+
|
653 |
+
return $response;
|
654 |
+
}
|
655 |
+
|
656 |
+
if ( ! function_exists( 'json_last_error_msg' ) ) :
|
657 |
+
/**
|
658 |
+
* Returns the error string of the last json_encode() or json_decode() call
|
659 |
+
*
|
660 |
+
* @internal This is a compatibility function for PHP <5.5
|
661 |
+
*
|
662 |
+
* @return boolean|string Returns the error message on success, "No Error" if no error has occurred, or FALSE on failure.
|
663 |
+
*/
|
664 |
+
function json_last_error_msg() {
|
665 |
+
// see https://core.trac.wordpress.org/ticket/27799
|
666 |
+
if ( ! function_exists( 'json_last_error' ) ) {
|
667 |
+
return false;
|
668 |
+
}
|
669 |
+
|
670 |
+
$last_error_code = json_last_error();
|
671 |
+
|
672 |
+
// just in case JSON_ERROR_NONE is not defined
|
673 |
+
$error_code_none = defined( 'JSON_ERROR_NONE' ) ? JSON_ERROR_NONE : 0;
|
674 |
+
|
675 |
+
switch ( true ) {
|
676 |
+
case $last_error_code === $error_code_none:
|
677 |
+
return 'No error';
|
678 |
+
|
679 |
+
case defined( 'JSON_ERROR_DEPTH' ) && JSON_ERROR_DEPTH === $last_error_code:
|
680 |
+
return 'Maximum stack depth exceeded';
|
681 |
+
|
682 |
+
case defined( 'JSON_ERROR_STATE_MISMATCH' ) && JSON_ERROR_STATE_MISMATCH === $last_error_code:
|
683 |
+
return 'State mismatch (invalid or malformed JSON)';
|
684 |
+
|
685 |
+
case defined( 'JSON_ERROR_CTRL_CHAR' ) && JSON_ERROR_CTRL_CHAR === $last_error_code:
|
686 |
+
return 'Control character error, possibly incorrectly encoded';
|
687 |
+
|
688 |
+
case defined( 'JSON_ERROR_SYNTAX' ) && JSON_ERROR_SYNTAX === $last_error_code:
|
689 |
+
return 'Syntax error';
|
690 |
+
|
691 |
+
case defined( 'JSON_ERROR_UTF8' ) && JSON_ERROR_UTF8 === $last_error_code:
|
692 |
+
return 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
693 |
+
|
694 |
+
case defined( 'JSON_ERROR_RECURSION' ) && JSON_ERROR_RECURSION === $last_error_code:
|
695 |
+
return 'Recursion detected';
|
696 |
+
|
697 |
+
case defined( 'JSON_ERROR_INF_OR_NAN' ) && JSON_ERROR_INF_OR_NAN === $last_error_code:
|
698 |
+
return 'Inf and NaN cannot be JSON encoded';
|
699 |
+
|
700 |
+
case defined( 'JSON_ERROR_UNSUPPORTED_TYPE' ) && JSON_ERROR_UNSUPPORTED_TYPE === $last_error_code:
|
701 |
+
return 'Type is not supported';
|
702 |
+
|
703 |
+
default:
|
704 |
+
return 'An unknown error occurred';
|
705 |
+
}
|
706 |
+
}
|
707 |
+
endif;
|
readme.txt
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== WordPress REST API (Version 2) ===
|
2 |
+
Contributors: rmccue, rachelbaker
|
3 |
+
Tags: json, rest, api, rest-api
|
4 |
+
Requires at least: 4.3-alpha
|
5 |
+
Tested up to: 4.3-beta
|
6 |
+
Stable tag: 2.0-beta3
|
7 |
+
License: GPLv2 or later
|
8 |
+
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
+
|
10 |
+
Access your site's data through an easy-to-use HTTP REST API. (Version 2)
|
11 |
+
|
12 |
+
== Description ==
|
13 |
+
WordPress is moving towards becoming a fully-fledged application framework, and we need new APIs. This project was born to create an easy-to-use, easy-to-understand and well-tested framework for creating these APIs, plus creating APIs for core.
|
14 |
+
|
15 |
+
This plugin provides an easy to use REST API, available via HTTP. Grab your site's data in simple JSON format, including users, posts, taxonomies and more. Retrieving or updating data is as simple as sending a HTTP request.
|
16 |
+
|
17 |
+
Want to get your site's posts? Simply send a `GET` request to `/wp-json/wp/v2/posts`. Update user with ID 4? Send a `PUT` request to `/wp-json/wp/v2/users/4`. Get all posts with the search term "awesome"? `GET /wp-json/wp/v2/posts?filter[s]=awesome`. It's that easy.
|
18 |
+
|
19 |
+
WP API exposes a simple yet easy interface to WP Query, the posts API, post meta API, users API, revisions API and many more. Chances are, if you can do it with WordPress, WP API will let you do it.
|
20 |
+
|
21 |
+
WP API also includes an easy-to-use Javascript API based on Backbone models, allowing plugin and theme developers to get up and running without needing to know anything about the details of getting connected.
|
22 |
+
|
23 |
+
Check out [our documentation][docs] for information on what's available in the API and how to use it. We've also got documentation on extending the API with extra data for plugin and theme developers!
|
24 |
+
|
25 |
+
All tickets for the project are being tracked on [GitHub][]. You can also take a look at the [recent updates][] for the project.
|
26 |
+
|
27 |
+
[docs]: http://v2.wp-api.org/
|
28 |
+
[GitHub]: https://github.com/WP-API/WP-API
|
29 |
+
[recent updates]: http://make.wp-api.org/
|
30 |
+
|
31 |
+
== Installation ==
|
32 |
+
|
33 |
+
Drop this directory in and activate it.
|
34 |
+
|
35 |
+
For full-flavoured API support, you'll need to be using pretty permalinks to use the plugin, as it uses custom rewrite rules to power the API.
|
36 |
+
|
37 |
+
== Changelog ==
|
38 |
+
|
39 |
+
= Version 2.0 Beta 1 =
|
40 |
+
|
41 |
+
Partial rewrite and evolution of the REST API to prepare for core integration.
|
42 |
+
|
43 |
+
For versions 0.x through 1.x, see the [legacy plugin changelog](https://wordpress.org/plugins/json-rest-api/changelog/).
|
wp-api.js
ADDED
@@ -0,0 +1,987 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function( window, undefined ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
function WP_API() {
|
6 |
+
this.models = {};
|
7 |
+
this.collections = {};
|
8 |
+
this.views = {};
|
9 |
+
}
|
10 |
+
|
11 |
+
window.wp = window.wp || {};
|
12 |
+
wp.api = wp.api || new WP_API();
|
13 |
+
|
14 |
+
})( window );
|
15 |
+
|
16 |
+
(function( Backbone, _, window, undefined ) {
|
17 |
+
|
18 |
+
//'use strict';
|
19 |
+
|
20 |
+
// ECMAScript 5 shim, from MDN
|
21 |
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
|
22 |
+
if ( ! Date.prototype.toISOString ) {
|
23 |
+
var pad = function( number ) {
|
24 |
+
var r = String( number );
|
25 |
+
if ( r.length === 1 ) {
|
26 |
+
r = '0' + r;
|
27 |
+
}
|
28 |
+
return r;
|
29 |
+
};
|
30 |
+
|
31 |
+
Date.prototype.toISOString = function() {
|
32 |
+
return this.getUTCFullYear() +
|
33 |
+
'-' + pad( this.getUTCMonth() + 1 ) +
|
34 |
+
'-' + pad( this.getUTCDate() ) +
|
35 |
+
'T' + pad( this.getUTCHours() ) +
|
36 |
+
':' + pad( this.getUTCMinutes() ) +
|
37 |
+
':' + pad( this.getUTCSeconds() ) +
|
38 |
+
'.' + String( ( this.getUTCMilliseconds()/1000 ).toFixed( 3 ) ).slice( 2, 5 ) +
|
39 |
+
'Z';
|
40 |
+
};
|
41 |
+
}
|
42 |
+
|
43 |
+
function WP_API_Utils() {
|
44 |
+
var origParse = Date.parse,
|
45 |
+
numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
|
46 |
+
|
47 |
+
|
48 |
+
this.parseISO8601 = function( date ) {
|
49 |
+
var timestamp, struct, i, k,
|
50 |
+
minutesOffset = 0;
|
51 |
+
|
52 |
+
// ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
|
53 |
+
// before falling back to any implementation-specific date parsing, so that’s what we do, even if native
|
54 |
+
// implementations could be faster
|
55 |
+
// 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
|
56 |
+
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))) {
|
57 |
+
// avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
|
58 |
+
for ( i = 0; ( k = numericKeys[i] ); ++i) {
|
59 |
+
struct[k] = +struct[k] || 0;
|
60 |
+
}
|
61 |
+
|
62 |
+
// allow undefined days and months
|
63 |
+
struct[2] = ( +struct[2] || 1 ) - 1;
|
64 |
+
struct[3] = +struct[3] || 1;
|
65 |
+
|
66 |
+
if ( struct[8] !== 'Z' && struct[9] !== undefined ) {
|
67 |
+
minutesOffset = struct[10] * 60 + struct[11];
|
68 |
+
|
69 |
+
if ( struct[9] === '+' ) {
|
70 |
+
minutesOffset = 0 - minutesOffset;
|
71 |
+
}
|
72 |
+
}
|
73 |
+
|
74 |
+
timestamp = Date.UTC( struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7] );
|
75 |
+
}
|
76 |
+
else {
|
77 |
+
timestamp = origParse ? origParse( date ) : NaN;
|
78 |
+
}
|
79 |
+
|
80 |
+
return timestamp;
|
81 |
+
};
|
82 |
+
}
|
83 |
+
|
84 |
+
window.wp = window.wp || {};
|
85 |
+
wp.api = wp.api || {};
|
86 |
+
wp.api.utils = wp.api.utils || new WP_API_Utils();
|
87 |
+
|
88 |
+
})( Backbone, _, window );
|
89 |
+
|
90 |
+
/* global WP_API_Settings:false */
|
91 |
+
// Suppress warning about parse function's unused "options" argument:
|
92 |
+
/* jshint unused:false */
|
93 |
+
(function( wp, WP_API_Settings, Backbone, _, window, undefined ) {
|
94 |
+
|
95 |
+
'use strict';
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Array of parseable dates
|
99 |
+
*
|
100 |
+
* @type {string[]}
|
101 |
+
*/
|
102 |
+
var parseable_dates = [ 'date', 'modified', 'date_gmt', 'modified_gmt' ];
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Mixin for all content that is time stamped
|
106 |
+
*
|
107 |
+
* @type {{toJSON: toJSON, parse: parse}}
|
108 |
+
*/
|
109 |
+
var TimeStampedMixin = {
|
110 |
+
/**
|
111 |
+
* Serialize the entity pre-sync
|
112 |
+
*
|
113 |
+
* @returns {*}
|
114 |
+
*/
|
115 |
+
toJSON: function() {
|
116 |
+
var attributes = _.clone( this.attributes );
|
117 |
+
|
118 |
+
// Serialize Date objects back into 8601 strings
|
119 |
+
_.each( parseable_dates, function ( key ) {
|
120 |
+
if ( key in attributes ) {
|
121 |
+
attributes[key] = attributes[key].toISOString();
|
122 |
+
}
|
123 |
+
});
|
124 |
+
|
125 |
+
return attributes;
|
126 |
+
},
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Unserialize the fetched response
|
130 |
+
*
|
131 |
+
* @param {*} response
|
132 |
+
* @returns {*}
|
133 |
+
*/
|
134 |
+
parse: function( response ) {
|
135 |
+
// Parse dates into native Date objects
|
136 |
+
_.each( parseable_dates, function ( key ) {
|
137 |
+
if ( ! ( key in response ) ) {
|
138 |
+
return;
|
139 |
+
}
|
140 |
+
|
141 |
+
var timestamp = wp.api.utils.parseISO8601( response[key] );
|
142 |
+
response[key] = new Date( timestamp );
|
143 |
+
});
|
144 |
+
|
145 |
+
// Parse the author into a User object
|
146 |
+
if ( response.author !== 'undefined' ) {
|
147 |
+
response.author = new wp.api.models.User( response.author );
|
148 |
+
}
|
149 |
+
|
150 |
+
return response;
|
151 |
+
}
|
152 |
+
};
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Mixin for all hierarchical content types such as posts
|
156 |
+
*
|
157 |
+
* @type {{parent: parent}}
|
158 |
+
*/
|
159 |
+
var HierarchicalMixin = {
|
160 |
+
/**
|
161 |
+
* Get parent object
|
162 |
+
*
|
163 |
+
* @returns {Backbone.Model}
|
164 |
+
*/
|
165 |
+
parent: function() {
|
166 |
+
|
167 |
+
var object, parent = this.get( 'parent' );
|
168 |
+
|
169 |
+
// Return null if we don't have a parent
|
170 |
+
if ( parent === 0 ) {
|
171 |
+
return null;
|
172 |
+
}
|
173 |
+
|
174 |
+
var parentModel = this;
|
175 |
+
|
176 |
+
if ( typeof this.parentModel !== 'undefined' ) {
|
177 |
+
/**
|
178 |
+
* Probably a better way to do this. Perhaps grab a cached version of the
|
179 |
+
* instantiated model?
|
180 |
+
*/
|
181 |
+
parentModel = new this.parentModel();
|
182 |
+
}
|
183 |
+
|
184 |
+
// Can we get this from its collection?
|
185 |
+
if ( parentModel.collection ) {
|
186 |
+
return parentModel.collection.get( parent );
|
187 |
+
} else {
|
188 |
+
// Otherwise, get the object directly
|
189 |
+
object = new parentModel.constructor( {
|
190 |
+
ID: parent
|
191 |
+
});
|
192 |
+
|
193 |
+
// Note that this acts asynchronously
|
194 |
+
object.fetch();
|
195 |
+
return object;
|
196 |
+
}
|
197 |
+
}
|
198 |
+
};
|
199 |
+
|
200 |
+
/**
|
201 |
+
* Private Backbone base model for all models
|
202 |
+
*/
|
203 |
+
var BaseModel = Backbone.Model.extend(
|
204 |
+
/** @lends BaseModel.prototype */
|
205 |
+
{
|
206 |
+
/**
|
207 |
+
* Set nonce header before every Backbone sync
|
208 |
+
*
|
209 |
+
* @param {string} method
|
210 |
+
* @param {Backbone.Model} model
|
211 |
+
* @param {{beforeSend}, *} options
|
212 |
+
* @returns {*}
|
213 |
+
*/
|
214 |
+
sync: function( method, model, options ) {
|
215 |
+
options = options || {};
|
216 |
+
|
217 |
+
if ( typeof WP_API_Settings.nonce !== 'undefined' ) {
|
218 |
+
var beforeSend = options.beforeSend;
|
219 |
+
|
220 |
+
options.beforeSend = function( xhr ) {
|
221 |
+
xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce );
|
222 |
+
|
223 |
+
if ( beforeSend ) {
|
224 |
+
return beforeSend.apply( this, arguments );
|
225 |
+
}
|
226 |
+
};
|
227 |
+
}
|
228 |
+
|
229 |
+
return Backbone.sync( method, model, options );
|
230 |
+
}
|
231 |
+
}
|
232 |
+
);
|
233 |
+
|
234 |
+
/**
|
235 |
+
* Backbone model for single users
|
236 |
+
*/
|
237 |
+
wp.api.models.User = BaseModel.extend(
|
238 |
+
/** @lends User.prototype */
|
239 |
+
{
|
240 |
+
idAttribute: 'ID',
|
241 |
+
|
242 |
+
urlRoot: WP_API_Settings.root + '/users',
|
243 |
+
|
244 |
+
defaults: {
|
245 |
+
ID: null,
|
246 |
+
username: '',
|
247 |
+
email: '',
|
248 |
+
password: '',
|
249 |
+
name: '',
|
250 |
+
first_name: '',
|
251 |
+
last_name: '',
|
252 |
+
nickname: '',
|
253 |
+
slug: '',
|
254 |
+
URL: '',
|
255 |
+
avatar: '',
|
256 |
+
meta: {
|
257 |
+
links: {}
|
258 |
+
}
|
259 |
+
},
|
260 |
+
|
261 |
+
/**
|
262 |
+
* Return avatar URL
|
263 |
+
*
|
264 |
+
* @param {number} size
|
265 |
+
* @returns {string}
|
266 |
+
*/
|
267 |
+
avatar: function( size ) {
|
268 |
+
return this.get( 'avatar' ) + '&s=' + size;
|
269 |
+
}
|
270 |
+
}
|
271 |
+
);
|
272 |
+
|
273 |
+
/**
|
274 |
+
* Model for Taxonomy
|
275 |
+
*/
|
276 |
+
wp.api.models.Taxonomy = BaseModel.extend(
|
277 |
+
/** @lends Taxonomy.prototype */
|
278 |
+
{
|
279 |
+
idAttribute: 'slug',
|
280 |
+
|
281 |
+
urlRoot: WP_API_Settings.root + '/taxonomies',
|
282 |
+
|
283 |
+
defaults: {
|
284 |
+
name: '',
|
285 |
+
slug: null,
|
286 |
+
labels: {},
|
287 |
+
types: {},
|
288 |
+
show_cloud: false,
|
289 |
+
hierarchical: false,
|
290 |
+
meta: {
|
291 |
+
links: {}
|
292 |
+
}
|
293 |
+
}
|
294 |
+
}
|
295 |
+
);
|
296 |
+
|
297 |
+
/**
|
298 |
+
* Backbone model for term
|
299 |
+
*/
|
300 |
+
wp.api.models.Term = BaseModel.extend( _.extend(
|
301 |
+
/** @lends Term.prototype */
|
302 |
+
{
|
303 |
+
idAttribute: 'ID',
|
304 |
+
|
305 |
+
taxonomy: 'category',
|
306 |
+
|
307 |
+
/**
|
308 |
+
* @class Represent a term
|
309 |
+
* @augments Backbone.Model
|
310 |
+
* @constructs
|
311 |
+
*/
|
312 |
+
initialize: function( attributes, options ) {
|
313 |
+
if ( typeof options !== 'undefined' ) {
|
314 |
+
if ( options.taxonomy ) {
|
315 |
+
this.taxonomy = options.taxonomy;
|
316 |
+
}
|
317 |
+
}
|
318 |
+
},
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Return URL for the model
|
322 |
+
*
|
323 |
+
* @returns {string}
|
324 |
+
*/
|
325 |
+
url: function() {
|
326 |
+
var id = this.get( 'ID' );
|
327 |
+
id = id || '';
|
328 |
+
|
329 |
+
return WP_API_Settings.root + '/taxonomies/' + this.taxonomy + '/terms/' + id;
|
330 |
+
},
|
331 |
+
|
332 |
+
defaults: {
|
333 |
+
ID: null,
|
334 |
+
name: '',
|
335 |
+
slug: '',
|
336 |
+
description: '',
|
337 |
+
parent: null,
|
338 |
+
count: 0,
|
339 |
+
link: '',
|
340 |
+
meta: {
|
341 |
+
links: {}
|
342 |
+
}
|
343 |
+
}
|
344 |
+
|
345 |
+
}, TimeStampedMixin, HierarchicalMixin )
|
346 |
+
);
|
347 |
+
|
348 |
+
/**
|
349 |
+
* Backbone model for single posts
|
350 |
+
*/
|
351 |
+
wp.api.models.Post = BaseModel.extend( _.extend(
|
352 |
+
/** @lends Post.prototype */
|
353 |
+
{
|
354 |
+
idAttribute: 'ID',
|
355 |
+
|
356 |
+
urlRoot: WP_API_Settings.root + '/posts',
|
357 |
+
|
358 |
+
defaults: {
|
359 |
+
ID: null,
|
360 |
+
title: '',
|
361 |
+
status: 'draft',
|
362 |
+
type: 'post',
|
363 |
+
author: new wp.api.models.User(),
|
364 |
+
content: '',
|
365 |
+
link: '',
|
366 |
+
'parent': 0,
|
367 |
+
date: new Date(),
|
368 |
+
date_gmt: new Date(),
|
369 |
+
modified: new Date(),
|
370 |
+
modified_gmt: new Date(),
|
371 |
+
format: 'standard',
|
372 |
+
slug: '',
|
373 |
+
guid: '',
|
374 |
+
excerpt: '',
|
375 |
+
menu_order: 0,
|
376 |
+
comment_status: 'open',
|
377 |
+
ping_status: 'open',
|
378 |
+
sticky: false,
|
379 |
+
date_tz: 'Etc/UTC',
|
380 |
+
modified_tz: 'Etc/UTC',
|
381 |
+
featured_image: null,
|
382 |
+
terms: {},
|
383 |
+
post_meta: {},
|
384 |
+
meta: {
|
385 |
+
links: {}
|
386 |
+
}
|
387 |
+
}
|
388 |
+
}, TimeStampedMixin, HierarchicalMixin )
|
389 |
+
);
|
390 |
+
|
391 |
+
/**
|
392 |
+
* Backbone model for pages
|
393 |
+
*/
|
394 |
+
wp.api.models.Page = BaseModel.extend( _.extend(
|
395 |
+
/** @lends Page.prototype */
|
396 |
+
{
|
397 |
+
idAttribute: 'ID',
|
398 |
+
|
399 |
+
urlRoot: WP_API_Settings.root + '/pages',
|
400 |
+
|
401 |
+
defaults: {
|
402 |
+
ID: null,
|
403 |
+
title: '',
|
404 |
+
status: 'draft',
|
405 |
+
type: 'page',
|
406 |
+
author: new wp.api.models.User(),
|
407 |
+
content: '',
|
408 |
+
parent: 0,
|
409 |
+
link: '',
|
410 |
+
date: new Date(),
|
411 |
+
modified: new Date(),
|
412 |
+
date_gmt: new Date(),
|
413 |
+
modified_gmt: new Date(),
|
414 |
+
date_tz: 'Etc/UTC',
|
415 |
+
modified_tz: 'Etc/UTC',
|
416 |
+
format: 'standard',
|
417 |
+
slug: '',
|
418 |
+
guid: '',
|
419 |
+
excerpt: '',
|
420 |
+
menu_order: 0,
|
421 |
+
comment_status: 'closed',
|
422 |
+
ping_status: 'open',
|
423 |
+
sticky: false,
|
424 |
+
password: '',
|
425 |
+
meta: {
|
426 |
+
links: {}
|
427 |
+
},
|
428 |
+
featured_image: null,
|
429 |
+
terms: []
|
430 |
+
}
|
431 |
+
}, TimeStampedMixin, HierarchicalMixin )
|
432 |
+
);
|
433 |
+
|
434 |
+
/**
|
435 |
+
* Backbone model for revisions
|
436 |
+
*/
|
437 |
+
wp.api.models.Revision = wp.api.models.Post.extend(
|
438 |
+
/** @lends Revision.prototype */
|
439 |
+
{
|
440 |
+
/**
|
441 |
+
* Return URL for model
|
442 |
+
*
|
443 |
+
* @returns {string}
|
444 |
+
*/
|
445 |
+
url: function() {
|
446 |
+
var parent_id = this.get( 'parent' );
|
447 |
+
parent_id = parent_id || '';
|
448 |
+
|
449 |
+
var id = this.get( 'ID' );
|
450 |
+
id = id || '';
|
451 |
+
|
452 |
+
return WP_API_Settings.root + '/posts/' + parent_id + '/revisions/' + id;
|
453 |
+
},
|
454 |
+
|
455 |
+
/**
|
456 |
+
* @class Represent a revision
|
457 |
+
* @augments Backbone.Model
|
458 |
+
* @constructs
|
459 |
+
*/
|
460 |
+
initialize: function() {
|
461 |
+
// Todo: what of the parent model is a page?
|
462 |
+
this.parentModel = wp.api.models.Post;
|
463 |
+
}
|
464 |
+
}
|
465 |
+
);
|
466 |
+
|
467 |
+
/**
|
468 |
+
* Backbone model for media items
|
469 |
+
*/
|
470 |
+
wp.api.models.Media = BaseModel.extend( _.extend(
|
471 |
+
/** @lends Media.prototype */
|
472 |
+
{
|
473 |
+
idAttribute: 'ID',
|
474 |
+
|
475 |
+
urlRoot: WP_API_Settings.root + '/media',
|
476 |
+
|
477 |
+
defaults: {
|
478 |
+
ID: null,
|
479 |
+
title: '',
|
480 |
+
status: 'inherit',
|
481 |
+
type: 'attachment',
|
482 |
+
author: new wp.api.models.User(),
|
483 |
+
content: '',
|
484 |
+
parent: 0,
|
485 |
+
link: '',
|
486 |
+
date: new Date(),
|
487 |
+
modified: new Date(),
|
488 |
+
format: 'standard',
|
489 |
+
slug: '',
|
490 |
+
guid: '',
|
491 |
+
excerpt: '',
|
492 |
+
menu_order: 0,
|
493 |
+
comment_status: 'open',
|
494 |
+
ping_status: 'open',
|
495 |
+
sticky: false,
|
496 |
+
date_tz: 'Etc/UTC',
|
497 |
+
modified_tz: 'Etc/UTC',
|
498 |
+
date_gmt: new Date(),
|
499 |
+
modified_gmt: new Date(),
|
500 |
+
meta: {
|
501 |
+
links: {}
|
502 |
+
},
|
503 |
+
terms: [],
|
504 |
+
source: '',
|
505 |
+
is_image: true,
|
506 |
+
attachment_meta: {},
|
507 |
+
image_meta: {}
|
508 |
+
},
|
509 |
+
|
510 |
+
/**
|
511 |
+
* @class Represent a media item
|
512 |
+
* @augments Backbone.Model
|
513 |
+
* @constructs
|
514 |
+
*/
|
515 |
+
initialize: function() {
|
516 |
+
// Todo: what of the parent model is a page?
|
517 |
+
this.parentModel = wp.api.models.Post;
|
518 |
+
}
|
519 |
+
}, TimeStampedMixin, HierarchicalMixin )
|
520 |
+
);
|
521 |
+
|
522 |
+
/**
|
523 |
+
* Backbone model for comments
|
524 |
+
*/
|
525 |
+
wp.api.models.Comment = BaseModel.extend( _.extend(
|
526 |
+
/** @lends Comment.prototype */
|
527 |
+
{
|
528 |
+
idAttribute: 'ID',
|
529 |
+
|
530 |
+
defaults: {
|
531 |
+
ID: null,
|
532 |
+
post: null,
|
533 |
+
content: '',
|
534 |
+
status: 'hold',
|
535 |
+
type: '',
|
536 |
+
parent: 0,
|
537 |
+
author: new wp.api.models.User(),
|
538 |
+
date: new Date(),
|
539 |
+
date_gmt: new Date(),
|
540 |
+
date_tz: 'Etc/UTC',
|
541 |
+
meta: {
|
542 |
+
links: {}
|
543 |
+
}
|
544 |
+
},
|
545 |
+
|
546 |
+
/**
|
547 |
+
* Return URL for model
|
548 |
+
*
|
549 |
+
* @returns {string}
|
550 |
+
*/
|
551 |
+
url: function() {
|
552 |
+
var post_id = this.get( 'post' );
|
553 |
+
post_id = post_id || '';
|
554 |
+
|
555 |
+
var id = this.get( 'ID' );
|
556 |
+
id = id || '';
|
557 |
+
|
558 |
+
return WP_API_Settings.root + '/posts/' + post_id + '/comments/' + id;
|
559 |
+
}
|
560 |
+
}, TimeStampedMixin, HierarchicalMixin )
|
561 |
+
);
|
562 |
+
|
563 |
+
/**
|
564 |
+
* Backbone model for single post types
|
565 |
+
*/
|
566 |
+
wp.api.models.PostType = BaseModel.extend(
|
567 |
+
/** @lends PostType.prototype */
|
568 |
+
{
|
569 |
+
idAttribute: 'slug',
|
570 |
+
|
571 |
+
urlRoot: WP_API_Settings.root + '/posts/types',
|
572 |
+
|
573 |
+
defaults: {
|
574 |
+
slug: null,
|
575 |
+
name: '',
|
576 |
+
description: '',
|
577 |
+
labels: {},
|
578 |
+
queryable: false,
|
579 |
+
searchable: false,
|
580 |
+
hierarchical: false,
|
581 |
+
meta: {
|
582 |
+
links: {}
|
583 |
+
},
|
584 |
+
taxonomies: []
|
585 |
+
},
|
586 |
+
|
587 |
+
/**
|
588 |
+
* Prevent model from being saved
|
589 |
+
*
|
590 |
+
* @returns {boolean}
|
591 |
+
*/
|
592 |
+
save: function () {
|
593 |
+
return false;
|
594 |
+
},
|
595 |
+
|
596 |
+
/**
|
597 |
+
* Prevent model from being deleted
|
598 |
+
*
|
599 |
+
* @returns {boolean}
|
600 |
+
*/
|
601 |
+
'delete': function () {
|
602 |
+
return false;
|
603 |
+
}
|
604 |
+
}
|
605 |
+
);
|
606 |
+
|
607 |
+
/**
|
608 |
+
* Backbone model for a post status
|
609 |
+
*/
|
610 |
+
wp.api.models.PostStatus = BaseModel.extend(
|
611 |
+
/** @lends PostStatus.prototype */
|
612 |
+
{
|
613 |
+
idAttribute: 'slug',
|
614 |
+
|
615 |
+
urlRoot: WP_API_Settings.root + '/posts/statuses',
|
616 |
+
|
617 |
+
defaults: {
|
618 |
+
slug: null,
|
619 |
+
name: '',
|
620 |
+
'public': true,
|
621 |
+
'protected': false,
|
622 |
+
'private': false,
|
623 |
+
queryable: true,
|
624 |
+
show_in_list: true,
|
625 |
+
meta: {
|
626 |
+
links: {}
|
627 |
+
}
|
628 |
+
},
|
629 |
+
|
630 |
+
/**
|
631 |
+
* Prevent model from being saved
|
632 |
+
*
|
633 |
+
* @returns {boolean}
|
634 |
+
*/
|
635 |
+
save: function() {
|
636 |
+
return false;
|
637 |
+
},
|
638 |
+
|
639 |
+
/**
|
640 |
+
* Prevent model from being deleted
|
641 |
+
*
|
642 |
+
* @returns {boolean}
|
643 |
+
*/
|
644 |
+
'delete': function() {
|
645 |
+
return false;
|
646 |
+
}
|
647 |
+
}
|
648 |
+
);
|
649 |
+
|
650 |
+
})( wp, WP_API_Settings, Backbone, _, window );
|
651 |
+
|
652 |
+
/* global WP_API_Settings:false */
|
653 |
+
(function( wp, WP_API_Settings, Backbone, _, window, undefined ) {
|
654 |
+
|
655 |
+
'use strict';
|
656 |
+
|
657 |
+
var BaseCollection = Backbone.Collection.extend(
|
658 |
+
/** @lends BaseCollection.prototype */
|
659 |
+
{
|
660 |
+
|
661 |
+
/**
|
662 |
+
* Setup default state
|
663 |
+
*/
|
664 |
+
initialize: function() {
|
665 |
+
this.state = {
|
666 |
+
data: {},
|
667 |
+
currentPage: null,
|
668 |
+
totalPages: null,
|
669 |
+
totalObjects: null
|
670 |
+
};
|
671 |
+
},
|
672 |
+
|
673 |
+
/**
|
674 |
+
* Overwrite Backbone.Collection.sync to pagination state based on response headers.
|
675 |
+
*
|
676 |
+
* Set nonce header before every Backbone sync.
|
677 |
+
*
|
678 |
+
* @param {string} method
|
679 |
+
* @param {Backbone.Model} model
|
680 |
+
* @param {{success}, *} options
|
681 |
+
* @returns {*}
|
682 |
+
*/
|
683 |
+
sync: function( method, model, options ) {
|
684 |
+
options = options || {};
|
685 |
+
var beforeSend = options.beforeSend;
|
686 |
+
|
687 |
+
if ( typeof WP_API_Settings.nonce !== 'undefined' ) {
|
688 |
+
options.beforeSend = function( xhr ) {
|
689 |
+
xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce );
|
690 |
+
|
691 |
+
if ( beforeSend ) {
|
692 |
+
return beforeSend.apply( this, arguments );
|
693 |
+
}
|
694 |
+
};
|
695 |
+
}
|
696 |
+
|
697 |
+
if ( 'read' === method ) {
|
698 |
+
var SELF = this;
|
699 |
+
|
700 |
+
if ( options.data ) {
|
701 |
+
SELF.state.data = _.clone( options.data );
|
702 |
+
|
703 |
+
delete SELF.state.data.page;
|
704 |
+
} else {
|
705 |
+
SELF.state.data = options.data = {};
|
706 |
+
}
|
707 |
+
|
708 |
+
if ( typeof options.data.page === 'undefined' ) {
|
709 |
+
SELF.state.currentPage = null;
|
710 |
+
SELF.state.totalPages = null;
|
711 |
+
SELF.state.totalObjects = null;
|
712 |
+
} else {
|
713 |
+
SELF.state.currentPage = options.data.page - 1;
|
714 |
+
}
|
715 |
+
|
716 |
+
var success = options.success;
|
717 |
+
options.success = function( data, textStatus, request ) {
|
718 |
+
SELF.state.totalPages = parseInt( request.getResponseHeader( 'X-WP-TotalPages' ), 10 );
|
719 |
+
SELF.state.totalObjects = parseInt( request.getResponseHeader( 'X-WP-Total' ), 10 );
|
720 |
+
|
721 |
+
if ( SELF.state.currentPage === null ) {
|
722 |
+
SELF.state.currentPage = 1;
|
723 |
+
} else {
|
724 |
+
SELF.state.currentPage++;
|
725 |
+
}
|
726 |
+
|
727 |
+
if ( success ) {
|
728 |
+
return success.apply( this, arguments );
|
729 |
+
}
|
730 |
+
};
|
731 |
+
}
|
732 |
+
|
733 |
+
return Backbone.sync( method, model, options );
|
734 |
+
},
|
735 |
+
|
736 |
+
/**
|
737 |
+
* Fetches the next page of objects if a new page exists
|
738 |
+
*
|
739 |
+
* @param {data: {page}} options
|
740 |
+
* @returns {*}
|
741 |
+
*/
|
742 |
+
more: function( options ) {
|
743 |
+
options = options || {};
|
744 |
+
options.data = options.data || {};
|
745 |
+
|
746 |
+
_.extend( options.data, this.state.data );
|
747 |
+
|
748 |
+
if ( typeof options.data.page === 'undefined' ) {
|
749 |
+
if ( ! this.hasMore() ) {
|
750 |
+
return false;
|
751 |
+
}
|
752 |
+
|
753 |
+
if ( this.state.currentPage === null || this.state.currentPage <= 1 ) {
|
754 |
+
options.data.page = 2;
|
755 |
+
} else {
|
756 |
+
options.data.page = this.state.currentPage + 1;
|
757 |
+
}
|
758 |
+
}
|
759 |
+
|
760 |
+
return this.fetch( options );
|
761 |
+
},
|
762 |
+
|
763 |
+
/**
|
764 |
+
* Returns true if there are more pages of objects available
|
765 |
+
*
|
766 |
+
* @returns null|boolean
|
767 |
+
*/
|
768 |
+
hasMore: function() {
|
769 |
+
if ( this.state.totalPages === null ||
|
770 |
+
this.state.totalObjects === null ||
|
771 |
+
this.state.currentPage === null ) {
|
772 |
+
return null;
|
773 |
+
} else {
|
774 |
+
return ( this.state.currentPage < this.state.totalPages );
|
775 |
+
}
|
776 |
+
}
|
777 |
+
}
|
778 |
+
);
|
779 |
+
|
780 |
+
/**
|
781 |
+
* Backbone collection for posts
|
782 |
+
*/
|
783 |
+
wp.api.collections.Posts = BaseCollection.extend(
|
784 |
+
/** @lends Posts.prototype */
|
785 |
+
{
|
786 |
+
url: WP_API_Settings.root + '/posts',
|
787 |
+
|
788 |
+
model: wp.api.models.Post
|
789 |
+
}
|
790 |
+
);
|
791 |
+
|
792 |
+
/**
|
793 |
+
* Backbone collection for pages
|
794 |
+
*/
|
795 |
+
wp.api.collections.Pages = BaseCollection.extend(
|
796 |
+
/** @lends Pages.prototype */
|
797 |
+
{
|
798 |
+
url: WP_API_Settings.root + '/pages',
|
799 |
+
|
800 |
+
model: wp.api.models.Page
|
801 |
+
}
|
802 |
+
);
|
803 |
+
|
804 |
+
/**
|
805 |
+
* Backbone users collection
|
806 |
+
*/
|
807 |
+
wp.api.collections.Users = BaseCollection.extend(
|
808 |
+
/** @lends Users.prototype */
|
809 |
+
{
|
810 |
+
url: WP_API_Settings.root + '/users',
|
811 |
+
|
812 |
+
model: wp.api.models.User
|
813 |
+
}
|
814 |
+
);
|
815 |
+
|
816 |
+
/**
|
817 |
+
* Backbone post statuses collection
|
818 |
+
*/
|
819 |
+
wp.api.collections.PostStatuses = BaseCollection.extend(
|
820 |
+
/** @lends PostStatuses.prototype */
|
821 |
+
{
|
822 |
+
url: WP_API_Settings.root + '/posts/statuses',
|
823 |
+
|
824 |
+
model: wp.api.models.PostStatus
|
825 |
+
|
826 |
+
}
|
827 |
+
);
|
828 |
+
|
829 |
+
/**
|
830 |
+
* Backbone media library collection
|
831 |
+
*/
|
832 |
+
wp.api.collections.MediaLibrary = BaseCollection.extend(
|
833 |
+
/** @lends MediaLibrary.prototype */
|
834 |
+
{
|
835 |
+
url: WP_API_Settings.root + '/media',
|
836 |
+
|
837 |
+
model: wp.api.models.Media
|
838 |
+
}
|
839 |
+
);
|
840 |
+
|
841 |
+
/**
|
842 |
+
* Backbone taxonomy collection
|
843 |
+
*/
|
844 |
+
wp.api.collections.Taxonomies = BaseCollection.extend(
|
845 |
+
/** @lends Taxonomies.prototype */
|
846 |
+
{
|
847 |
+
model: wp.api.models.Taxonomy,
|
848 |
+
|
849 |
+
url: WP_API_Settings.root + '/taxonomies'
|
850 |
+
}
|
851 |
+
);
|
852 |
+
|
853 |
+
/**
|
854 |
+
* Backbone comment collection
|
855 |
+
*/
|
856 |
+
wp.api.collections.Comments = BaseCollection.extend(
|
857 |
+
/** @lends Comments.prototype */
|
858 |
+
{
|
859 |
+
model: wp.api.models.Comment,
|
860 |
+
|
861 |
+
post: null,
|
862 |
+
|
863 |
+
/**
|
864 |
+
* @class Represent an array of comments
|
865 |
+
* @augments Backbone.Collection
|
866 |
+
* @constructs
|
867 |
+
*/
|
868 |
+
initialize: function( models, options ) {
|
869 |
+
BaseCollection.prototype.initialize.apply( this, arguments );
|
870 |
+
|
871 |
+
if ( options && options.post ) {
|
872 |
+
this.post = options.post;
|
873 |
+
}
|
874 |
+
},
|
875 |
+
|
876 |
+
/**
|
877 |
+
* Return URL for collection
|
878 |
+
*
|
879 |
+
* @returns {string}
|
880 |
+
*/
|
881 |
+
url: function() {
|
882 |
+
return WP_API_Settings.root + '/posts/' + this.post + '/comments';
|
883 |
+
}
|
884 |
+
}
|
885 |
+
);
|
886 |
+
|
887 |
+
/**
|
888 |
+
* Backbone post type collection
|
889 |
+
*/
|
890 |
+
wp.api.collections.PostTypes = BaseCollection.extend(
|
891 |
+
/** @lends PostTypes.prototype */
|
892 |
+
{
|
893 |
+
model: wp.api.models.PostType,
|
894 |
+
|
895 |
+
url: WP_API_Settings.root + '/posts/types'
|
896 |
+
}
|
897 |
+
);
|
898 |
+
|
899 |
+
/**
|
900 |
+
* Backbone terms collection
|
901 |
+
*/
|
902 |
+
wp.api.collections.Terms = BaseCollection.extend(
|
903 |
+
/** @lends Terms.prototype */
|
904 |
+
{
|
905 |
+
model: wp.api.models.Term,
|
906 |
+
|
907 |
+
type: 'post',
|
908 |
+
|
909 |
+
taxonomy: 'category',
|
910 |
+
|
911 |
+
/**
|
912 |
+
* @class Represent an array of terms
|
913 |
+
* @augments Backbone.Collection
|
914 |
+
* @constructs
|
915 |
+
*/
|
916 |
+
initialize: function( models, options ) {
|
917 |
+
BaseCollection.prototype.initialize.apply( this, arguments );
|
918 |
+
|
919 |
+
if ( typeof options !== 'undefined' ) {
|
920 |
+
if ( options.type ) {
|
921 |
+
this.type = options.type;
|
922 |
+
}
|
923 |
+
|
924 |
+
if ( options.taxonomy ) {
|
925 |
+
this.taxonomy = options.taxonomy;
|
926 |
+
}
|
927 |
+
}
|
928 |
+
|
929 |
+
this.on( 'add', _.bind( this.addModel, this ) );
|
930 |
+
},
|
931 |
+
|
932 |
+
/**
|
933 |
+
* We need to set the type and taxonomy for each model
|
934 |
+
*
|
935 |
+
* @param {Backbone.model} model
|
936 |
+
*/
|
937 |
+
addModel: function( model ) {
|
938 |
+
model.type = this.type;
|
939 |
+
model.taxonomy = this.taxonomy;
|
940 |
+
},
|
941 |
+
|
942 |
+
/**
|
943 |
+
* Return URL for collection
|
944 |
+
*
|
945 |
+
* @returns {string}
|
946 |
+
*/
|
947 |
+
url: function() {
|
948 |
+
return WP_API_Settings.root + '/posts/types/' + this.type + '/taxonomies/' + this.taxonomy + '/terms/';
|
949 |
+
}
|
950 |
+
}
|
951 |
+
);
|
952 |
+
|
953 |
+
/**
|
954 |
+
* Backbone revisions collection
|
955 |
+
*/
|
956 |
+
wp.api.collections.Revisions = BaseCollection.extend(
|
957 |
+
/** @lends Revisions.prototype */
|
958 |
+
{
|
959 |
+
model: wp.api.models.Revision,
|
960 |
+
|
961 |
+
parent: null,
|
962 |
+
|
963 |
+
/**
|
964 |
+
* @class Represent an array of revisions
|
965 |
+
* @augments Backbone.Collection
|
966 |
+
* @constructs
|
967 |
+
*/
|
968 |
+
initialize: function( models, options ) {
|
969 |
+
BaseCollection.prototype.initialize.apply( this, arguments );
|
970 |
+
|
971 |
+
if ( options && options.parent ) {
|
972 |
+
this.parent = options.parent;
|
973 |
+
}
|
974 |
+
},
|
975 |
+
|
976 |
+
/**
|
977 |
+
* return URL for collection
|
978 |
+
*
|
979 |
+
* @returns {string}
|
980 |
+
*/
|
981 |
+
url: function() {
|
982 |
+
return WP_API_Settings.root + '/posts/' + this.parent + '/revisions';
|
983 |
+
}
|
984 |
+
}
|
985 |
+
);
|
986 |
+
|
987 |
+
})( wp, WP_API_Settings, Backbone, _, window );
|