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.
annotationFront-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_annotationedit_annotationdelete_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_annotationsdelete_annotationsdelete_others_annotationsdelete_private_annotationsdelete_published_annotationsedit_annotationsedit_others_annotationsedit_private_annotationsedit_published_annotationspublish_annotationsread_private_annotations