WP Annotation Comments
https://github.com/WordPress/gutenberg/pull/4386
This is an alternative to annotations as a custom post type in #4385
Create a new annotation.
Note: While the API supports both front and back-end annotations, as a security precaution, front-end annotations are disabled for now. At this time, the main focus is on back-end annotations only.
Request parameters
Post password (if password-protected and authenticated user is unable to edit it, but is allowed to annotate; e.g., edge case in front-end annotations). Defaults to current password cookie.
Request body
Post ID.
An annotation comment type.
annotation
Front-end annotation (default).
Back-end annotation.
Parent ID, if it’s a reply.
Author’s user ID. Requires ability to edit_others_annotations
. Default is that of the authenticated user. Default is 0
for front-end anonymous annotations; i.e., if those are allowed by the rest_allow_anonymous_comments
filter.
Author’s name. Default is that of the authenticated user.
Author’s email address. Default is that of the authenticated user.
Author’s URL. Default is that of the authenticated user.
Author’s IP address. Requires ability to edit_others_annotations
. Default value is auto-detected using $_SERVER['REMOTE_ADDR']
.
Author’s user-agent (browser). Requires ability to edit_others_annotations
. Default value is auto-detected using $_SERVER['HTTP_USER_AGENT']
.
Comment content. Back-end annotations are not spam or flood checked. Front-end annotations follow the same rules as other comment types. Both front and back-end annotations have a maximum length.
W3C annotation selector. Max JSON-encoded size is 16kb. SVG selectors allow up to 128kbs, but those are currently disabled, pending a security review. See also: https://jsfiddle.net/jaswrks/jpcL1t8a/
{
"type": "CssSelector",
"value": "#foo > .bar"
}
{
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(3)",
"refinedBy": {
"type": "TextPositionSelector",
"start": 412,
"end": 667
}
}
{
"type": "RangeSelector",
"startSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(2)"
},
"endSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(8)"
}
}
Type of selector.
Can select text nodes!
Pending a security review.
Required for CssSelector type. Other properties apply to other types.
The default status for a new back-end annotation is approve
. The default status for a new front-end annotation is determined by wp_allow_comment(). To create an annotation with a restricted status (hold
, 0
, spam
, trash
) you must be able to edit_others_annotations
.
Held for moderation. Same as ‘0’.
Same as ‘1’ and ‘approve’.
Same as ‘1’ and ‘approved’.
Archived as resolved.
Archived as rejected.
Archived for another reason.
Front-end annotation spam.
In the trash.
Any comment meta properties registered using register_meta(). Requires an authenticated user that can edit_annotations
.
{
"my_key": "my value",
"another_key": {
"another": "value"
}
}
Any value.
Examples
i.e., Also supports all the same arguments as the REST API for comments.
{
"post": 1,
"type": "admin_annotation",
"content": "Hello world!",
"via": "gutenberg"
}
{
"post": 1,
"type": "admin_annotation",
"content": "Hello world!",
"via": "gutenberg",
"selector": {
"type": "CssSelector",
"value": "#foo > .bar"
}
}
{
"post": 1,
"type": "admin_annotation",
"author": 1,
"author_name": "John Smith",
"author_email": "john@example.com",
"author_url": "https://john.example.com/",
"author_ip": "127.0..0.1",
"author_user_agent": "Mozilla/5.0",
"content": "Hello world!",
"via": "gutenberg",
"selector": {
"type": "CSSSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(3)",
"refinedBy": {
"type": "TextPositionSelector",
"start": 412,
"end": 667
}
}
}
{
"post": 1,
"type": "admin_annotation",
"author": 1,
"author_name": "John Smith",
"author_email": "john@example.com",
"author_url": "https://john.example.com/",
"author_ip": "127.0..0.1",
"author_user_agent": "Mozilla/5.0",
"content": "Hello world!",
"via": "gutenberg",
"selector": {
"type": "RangeSelector",
"startSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(2)"
},
"endSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(8)"
}
},
"meta": {
"my_key": "my value",
"another_key": {
"another": "value"
}
}
}
{id}
Get a single annotation by ID.
Path variables
Annotation ID.
Request parameters
REST API context.
Post password (if protected and the authenticated user is unable to edit it, but is allowed to view). Defaults to current password cookie.
?context=view
{
"id": 1,
"post": 1,
"parent": 0,
"author": 1,
"author_name": "John",
"author_url": "https://example.john.com/",
"date": "2018-01-11T11:09:03",
"date_gmt": "2018-01-11T11:09:03",
"content": {
"rendered": "<p>Hello world!</p>\n"
},
"link": "https://example.com/hello-world/#comment-1",
"status": "approved",
"type": "admin_annotation",
"author_avatar_urls": {
"24": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=24&d=mm&r=g",
"48": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=48&d=mm&r=g",
"96": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=96&d=mm&r=g"
},
"meta": [],
"via": "gutenberg",
"selector": {
"type": "CssSelector",
"value": "#foo > .bar"
},
"_links": {
"self": [
{
"href": "https://example.com/wp-json/wp/v2/annotations/1"
}
],
"collection": [
{
"href": "https://example.com/wp-json/wp/v2/annotations"
}
],
"author": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/users/1"
}
],
"up": [
{
"embeddable": true,
"post_type": "post",
"href": "https://example.com/wp-json/wp/v2/posts/1"
}
]
}
}
?context=edit
{
"id": 1,
"post": 1,
"parent": 0,
"author": 1,
"author_name": "John",
"author_email": "john@example.com",
"author_url": "https://example.john.com/",
"author_ip": "192.168.42.1",
"author_user_agent": "Mozilla/5.0",
"date": "2018-01-11T11:09:03",
"date_gmt": "2018-01-11T11:09:03",
"content": {
"rendered": "<p>Hello world!</p>\n",
"raw": "Hello world!"
},
"link": "https://example.com/hello-world/#comment-1",
"status": "approved",
"type": "admin_annotation",
"author_avatar_urls": {
"24": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=24&d=mm&r=g",
"48": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=48&d=mm&r=g",
"96": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=96&d=mm&r=g"
},
"meta": [],
"via": "gutenberg",
"selector": {
"type": "CssSelector",
"value": "#foo > .bar"
},
"_links": {
"self": [
{
"href": "https://example.com/wp-json/wp/v2/annotations/1"
}
],
"collection": [
{
"href": "https://example.com/wp-json/wp/v2/annotations"
}
],
"author": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/users/1"
}
],
"up": [
{
"embeddable": true,
"post_type": "post",
"href": "https://example.com/wp-json/wp/v2/posts/1"
}
]
}
}
{id}
Update annotation properties.
Path variables
Annotation ID.
Request parameters
Post password (if password-protected and authenticated user is unable to edit it, but is allowed to edit annotations; e.g., edge case in front-end annotations). Defaults to current password cookie.
Request body
Author’s user ID. Requires ability to edit_others_annotations
.
Author’s name. Requires ability to edit_others_annotations
.
Author’s email address. Requires ability to edit_others_annotations
.
Author’s URL. Requires ability to edit_others_annotations
.
Author’s IP address. Requires ability to edit_others_annotations
.
Author’s user-agent (browser). Requires ability to edit_others_annotations
.
Comment content. Back-end annotations are not spam or flood checked. Front-end annotations follow the same rules as other comment types. Both front and back-end annotations have a maximum length.
W3C annotation selector. Max JSON-encoded size is 16kb. SVG selectors allow up to 128kbs, but those are currently disabled, pending a security review. See also: https://jsfiddle.net/jaswrks/jpcL1t8a/
{
"type": "CssSelector",
"value": "#foo > .bar"
}
{
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(3)",
"refinedBy": {
"type": "TextPositionSelector",
"start": 412,
"end": 667
}
}
{
"type": "RangeSelector",
"startSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(2)"
},
"endSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(8)"
}
}
Type of selector.
Can select text nodes!
Pending a security review.
Required for CssSelector type. Other properties apply to other types.
To set a restricted status (hold
, 0
, spam
, trash
) you must be able to edit_others_annotations
. However, if the authenticated user is the annotation author, and they can delete_annotation
, trash
becomes available.
Held for moderation. Same as ‘0’.
Same as ‘1’ and ‘approve’.
Same as ‘1’ and ‘approved’.
Archived as resolved.
Archived as rejected.
Archived for another reason.
Front-end annotation spam.
In the trash.
Any comment meta properties registered using register_meta().
{
"my_key": "my value",
"another_key": {
"another": "value"
}
}
Any value.
Examples
i.e., Also supports all the same arguments as the REST API for comments, except that post
, type
, and parent
are currently readonly in annotation comment types.
{
"content": "Hello world!"
}
{
"status": "resolve"
}
{
"status": "reject"
}
{
"status": "archive"
}
{
"author": 1,
"author_name": "John Smith",
"author_email": "john@example.com",
"author_url": "https://john.example.com/",
"author_ip": "127.0..0.1",
"author_user_agent": "Mozilla/5.0",
"content": "Hello world!",
"status": "approved",
"via": "gutenberg",
"selector": {
"type": "CSSSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(3)",
"refinedBy": {
"type": "TextPositionSelector",
"start": 412,
"end": 667
}
},
"meta": {
"my_key": "my value",
"another_key": {
"another": "value"
}
}
}
{id}
Delete a single annotation by ID.
Path variables
Annotation ID.
Request parameters
Bypass trash and force deletion?
Trash (default).
Permanently delete.
Post password (if protected and the authenticated user is unable to edit it, but is allowed to delete — edge case).
Also supports all the same arguments as the REST API for comments; e.g., pagination, order.
Request parameters
REST API context.
Post ID. Array or comma-delimited string.
Annotation comment type. Array or comma-delimited string.
Password for a single post (if protected and the authenticated user is unable to edit it, but is allowed to view). @TODO Support multiple passwords indexed by order of post
given in query.
Parent annotation ID. Array or comma-delimited string. If hierarchical
is given, parent
defaults to 0
unless defined explicitly. This way hiearchical queries start from top-level annotations by default.
Author ID. Requires ability to edit_others_annotations
. However, any authenticated user can set this to their own user ID, which allows them to query their own annotations.
Status. Array or comma-delimited string. To query a restricted status (hold
, 0
, spam
, trash
) you must be able to edit_others_annotations
. However, if author
is given, and the authenticated user is that author, and they can delete_annotations
, trash
becomes available.
Annotation client identifier. Array or comma-delimited string.
Hierarchical response format?
All children (deeply) are simply appended.
Each annotation includes a children
property containing an array of any replies.
?context=view&hierarchical=threaded
[
{
"id": 1,
"post": 1,
"parent": 0,
"author": 1,
"author_name": "John",
"author_url": "https://john.example.com",
"date": "2018-01-11T12:21:10",
"date_gmt": "2018-01-11T12:21:10",
"content": {
"rendered": "<p>Hello world!</p>\n"
},
"link": "https://example.com/hello-world/#comment-1",
"status": "approved",
"type": "admin_annotation",
"author_avatar_urls": {
"24": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=24&d=mm&r=g",
"48": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=48&d=mm&r=g",
"96": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=96&d=mm&r=g"
},
"meta": [],
"via": "gutenberg",
"selector": {
"type": "CssSelector",
"value": "#foo"
},
"_links": {
"self": [
{
"href": "https://example.com/wp-json/wp/v2/annotations/1"
}
],
"collection": [
{
"href": "https://example.com/wp-json/wp/v2/annotations"
}
],
"author": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/users/1"
}
],
"up": [
{
"embeddable": true,
"post_type": "post",
"href": "https://example.com/wp-json/wp/v2/posts/1"
}
]
},
"children": [
{
"id": 2,
"post": 1,
"parent": 1,
"author": 2,
"author_name": "Mary",
"author_url": "https://mary.example.com",
"date": "2018-01-11T12:21:10",
"date_gmt": "2018-01-11T12:21:10",
"content": {
"rendered": "<p>Hello world!</p>\n"
},
"link": "https://example.com/hello-world/#comment-2",
"status": "approved",
"type": "admin_annotation",
"author_avatar_urls": {
"24": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=24&d=mm&r=g",
"48": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=48&d=mm&r=g",
"96": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=96&d=mm&r=g"
},
"meta": [],
"via": "gutenberg",
"selector": null,
"_links": {
"self": [
{
"href": "https://example.com/wp-json/wp/v2/annotations/2"
}
],
"collection": [
{
"href": "https://example.com/wp-json/wp/v2/annotations"
}
],
"author": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/users/2"
}
],
"up": [
{
"embeddable": true,
"post_type": "post",
"href": "https://example.com/wp-json/wp/v2/posts/1"
}
]
},
"children": []
}
]
}
]
?context=edit&hierarchical=flat
[
{
"id": 1,
"post": 1,
"parent": 0,
"author": 1,
"author_name": "John",
"author_email": "john@example.com",
"author_url": "https://john.example.com",
"author_ip": "192.168.42.1",
"author_user_agent": "Mozilla/5.0",
"date": "2018-01-11T12:21:10",
"date_gmt": "2018-01-11T12:21:10",
"content": {
"rendered": "<p>Hello world!</p>\n",
"raw": "Hello world!"
},
"link": "https://wp.vm/hello-world/#comment-1",
"status": "approved",
"type": "admin_annotation",
"author_avatar_urls": {
"24": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=24&d=mm&r=g",
"48": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=48&d=mm&r=g",
"96": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=96&d=mm&r=g"
},
"meta": [],
"via": "gutenberg",
"selector": {
"type": "CssSelector",
"value": "#foo"
},
"_links": {
"self": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations/1"
}
],
"collection": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations"
}
],
"author": [
{
"embeddable": true,
"href": "https://wp.vm/wp-json/wp/v2/users/1"
}
],
"up": [
{
"embeddable": true,
"post_type": "post",
"href": "https://wp.vm/wp-json/wp/v2/posts/1"
}
]
}
},
{
"id": 2,
"post": 1,
"parent": 1,
"author": 2,
"author_name": "Mary",
"author_email": "mary@example.com",
"author_url": "https://mary.example.com",
"author_ip": "192.168.42.1",
"author_user_agent": "Mozilla/5.0",
"date": "2018-01-11T12:21:10",
"date_gmt": "2018-01-11T12:21:10",
"content": {
"rendered": "<p>Hello world!</p>\n",
"raw": "Hello world!"
},
"link": "https://wp.vm/hello-world/#comment-2",
"status": "approved",
"type": "admin_annotation",
"author_avatar_urls": {
"24": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=24&d=mm&r=g",
"48": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=48&d=mm&r=g",
"96": "https://secure.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=96&d=mm&r=g"
},
"meta": [],
"via": "gutenberg",
"selector": null,
"_links": {
"self": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations/2"
}
],
"collection": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations"
}
],
"author": [
{
"embeddable": true,
"href": "https://wp.vm/wp-json/wp/v2/users/2"
}
],
"up": [
{
"embeddable": true,
"post_type": "post",
"href": "https://wp.vm/wp-json/wp/v2/posts/1"
}
]
}
}
]
Annotation permissions sometimes resemble those associated with other comment types, but at other times (e.g., back-end annotations) they are not like other comments. So these additional caps offer a standard method by which to check annotation permissions.
Meta-caps check specific annotations by ID.
current_user_can( 'edit_annotation', 53254 )
read_annotation
edit_annotation
delete_annotation
This custom meta-cap checks a post by ID and the would-be comment type.
current_user_can( 'create_annotation', 123, 'admin_annotation' )
create_annotation
General pseudo-caps that optionally support args: post ID, comment type.
current_user_can( 'read_annotations', 123, 'admin_annotation' )
current_user_can( 'read_annotations' )
read_annotations
General pseudo-caps that optionally support arg: comment type.
current_user_can( 'edit_others_annotations', 'admin_annotation' )
current_user_can( 'edit_others_annotations' )
create_annotations
delete_annotations
delete_others_annotations
delete_private_annotations
delete_published_annotations
edit_annotations
edit_others_annotations
edit_private_annotations
edit_published_annotations
publish_annotations
read_private_annotations