WordPress comments package

Ok, thanks @inamkbmail.

I guess that means we can’t support WP.com comments with the same, non-authenticated approach. Maybe we need two separate packages, wp-org-comments and, in the future, wp-com-comments.

What do you think @david?

What I feel is maybe no, we don’t need separate package. We’d follow a better approach. Because all wp user use some sort of security plugins they may interfere and there will be a lot of issues as number of people increase.
Why not make package which follow this method https://developer.wordpress.com/docs/api/1.1/post/sites/%24site/posts/%24post_ID/replies/new/ and find a wp.org plugin or make a new one which simply enable oauth authentication similar to wp.com?

Thanks for the info. It seems like that endpoint still requires authentication, doesn’t it?


It seems like the problem with wp-comments-post.php in WP.com is not the file itself, but Akismet.

I was able to generate a new comment in a WP.com site without authenticating, with this payload:
https://luisherranz.wordpress.com/2017/10/02/first-blog-post/

highlander_comment_nonce: 563aafcdd9
_wp_http_referer: /2017/10/02/first-blog-post/
hc_post_as: guest
comment: Test
email: [email protected]
author: SomeAuthor
comment_post_ID: 4
comment_parent: 0
akismet_comment_nonce: 74d8025d64
genseq: 1586330353
ak_js: 1586330472903

highlander_comment_nonce and akismet_comment_nonce are available in the HTML, we could do a fetch and extract those.

But ak_js is not. My guess is that that number is generated using JavaScript and if you don’t send it, the request will fail.

We would have to study if it’s possible to create comments from an external service when Akismet is activated. @David and @inamkbmail, could you please take a look at that and report what you find?

For what I see in the code of the old package:

we didn’t even use nonces.

How is that possible? Do you remember, @david?

Maybe a good news but if you simply change value of

ak_js: 1586330472903

to something random like

ak_js: 5325

comment get posted…
I just commented on your web with ak_js value 123456 and it worked

Your this statement is not standing correct.

Actually I tried changing values of each to 123456 and comment got submitted but when I altered value of highlander_comment_nonce it brought up the same error like 'comment couldn’t be posted`. If

and we can get that nonce then we’re close to the solution.

Sorry I sent a few comments on your web please remove them from moderation awaiting list.

Maybe it depends on the type of WordPress you are using (.org or .com) and / or the plugins you have installed.

I did a quick test on a local instance of WordPress and this is the only information that was sent (there aren’t headers related to nonce either):

comment: Hello World!
author: SomeAuthor
email: [email protected]
url: https://frontity.org
submit: Post Comment
comment_post_ID: 2003
comment_parent: 0

On the contrary, thanks for testing it out :slight_smile:

I think we need to understand better how Akismet works because it is activated by default on WP.com but it is also widely used in self-hosted WP. Same for Jetpack.

If our comments package doesn’t work with Akismet or Jetpack, Frontity itself will be much less appealing.

I think we need to understand better how Akismet and JetPack work before we start coding.

Here it is the development API of Akismet. In libraries, there a couple of Node/JS libraries you can take a look:

Also, we need to understand what is that ak_js value used for:

Finally, we need to understand how the JetPack comments module works:

2 Likes

Heyy, I was all this week investigating how to make comments work on wordpress.com, mainly focused on Akismet and nonces, and I discovered that nonces can be ignored after all.

I did some tests and I was able to send a comment using Postman to a sample site I have in wordpress.com, just sendind this as x-www-from-urlencoded

comment:Hi all!
author:David
email:[email protected]
url:https://somesite.test
comment_post_ID:34
comment_parent:0

and setting this headers:

user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1

The only thing causing trouble is CORS, as request from a Frontity site to a wordpress.com one are made from a different origin, and browers doesn’t allow that. I guess that could be fixed creating a server extension that would act as a proxy, sending the comments from the Node.js server instead of doing that from the browser.

If you are courious, this is the workflow Akismet does for comments:

Sketch: https://excalidraw.com/#json=5675757456064512,zd4Srp3mMDqM1_6a4pMMBg

I did several tests with Akismet in a local WordPress instance and comments where published event without nonce or an invalid nonce, also checking that the nonce verification was failing, so that part doesn’t seem to affect.

1 Like

Does it send something indicating that it had a proper nonce to the Akismet server or the request to the Akismet server is equal in both cases?

The result of evaluating the nonce is sent along with the comment to the Akismet server, so it will receive 'failed' or 'passed' for the $comment['nonce'], if this is what you are asking.

Ok, so I guess that Akismet may be using that in its algorithm. For example:

  • Nonce: passed => chances to be spam, 40%.
  • Nonce: failed or inactive => chances to be spam, 70%.

Even though it is not required, right?

We I am trying to see if we can or cannot assume that the presence of the nonce is ignored by the Akismet server.

It may be, actually. But I don’t know. None of the commets I posted during the tests were marked as spam.

Regarding Akismet’s nonces, I guess a better way would be to disable them in the WordPress site, if possible. That way comments would be sent with $comment['nonce'] set as inactive which it seems to be a better value than failed (we don’t know how Akismet handles that value, though).

I said “if possible” because disabling nonces is something you can’t do on wordpress.com as it must be done programmatically, I mean, there is not an option in the GUI to do so.

For wordpress.com sites maybe the nonces can be extracted from the HTML code of the WordPress page, but for wordpress.org sites we have to take into consideration that the HTML code from WordPress cannot be readed in the case the theme bridge is used.

Also, a proxy would be needed anyway for posting comments form Frontity sites using a wordpress.com source as they would be in a different domain.

To summarize:

WP.com comments (with Akismet)

  • A proxy would be needed to post comments using /wp-comments-post.php
  • Nonces cannot be deactivated, but we can get them from the HTML or ignore them

WP.org comments (with Akismet)

  • No proxy needed (not 100% sure, it may depend on the hosting?)
  • Nonces can be deactivated programmatically
  • Nonces should not be obtained from the HTML (that won’t work for the PHP theme bridge)

@dev-team, before writing down the implementation proposal, could you take a look at this thread and give some feedback? Just to be sure I’m going to the right direction. :slightly_smiling_face:

I’ve read the whole thread and it sounds like yeah, you’re going in the right direction :slightly_smiling_face:

There are a couple of things that I miss in my mental model though:

  1. What do you mean by a “proxy server” in this context? I think that you mean extending the nodejs frontity server with an endpoint that you can call from the client. And then that endpoint will can call wp-comments-post.php on wp.com or wp.org. But I’m not 100% sure if that’s what you mean :slight_smile:
  2. I don’t really know why we need the proxy on wp.com but (probably) not on wp.org?
  3. Why should we not obtain the nonce from the HTML on wp.org and why does it not work with the Theme Bridge?

My mental model of the theme bridge was that it just passes on whatever HTML the frontity server has rendered. So I thought that the nonce should be included in that HTML that the frontity server passes to the theme bridge?

You can explain later or during the daily if that’s more convenient for you - it shouldn’t block you from continuing - it’s more for my own understanding and maybe just maybe stimulates some insight :sweat_smile:

Thanks for the research @david.

WP.com

I guess we could add that in the future once we add server extensibility, right? Something like:

export const server = ({ app }) => {
  app.use(get("wp-comments/post"), (ctx) => {
    // Do the call to /wp-comments-post.php here.
  });
};

Yeah, maybe this server function can do a fetch of a WP.com HTML to get one.

With those two things, I guess commenting on WP.com is possible. Maybe we also need to change the User Agent of the call to /wp-comments-post.php to simulate a browser call.


WP.org

It may depend on the hosting, yes. Maybe in the future we can always use the proxy, but for now if people have this problem they can fix it themselves.

We can create and send a comment nonce to the Frontity server on the request. Then, the wp-comments package can use it for posting.

$comment_nonce = wp_create_nonce("comment_nonce");
$html = wp_remote_get($frontity_server_url . "?_wp_nonce_comment=" . $comment_nonce);

Then, we eliminate all the _wp_nonce_xxx queries sent by the Theme Bridge with either Router Converters or filters (whatever comes first).


Ok, so I’d say, let’s do the first version, only for WP.org similar to what we had in the old version, and let’s move the proxy and Theme Bridge nonces to different FD to be addressed in the future, right?

Implementation proposal

At this moment we just want to implement the first two user stories, the one to fetch comments and get them from the state, and the other one to publish new comments:

As a Frontity user
I want to be able to access my WordPress comments in the state
so that I can consume them however I want

As a Frontity user
I want to be able to call an action to post a new comment
so that I can integrate it in my Comments forms

It is out of scope to export any React component from this package (like a comments list or publish form), although we need to write examples in any of our starter themes showing how to use the package.

actions.comments.create

Publish a new comment with the specified content.

Usage

actions.comments.create({
  comment: "This is awesome, thanks!",
  author: "David Arenas",
  email: "[email protected]",
  url = "https://frontity.org",
  postId: 60,
  parentId: 0,
});

Arguments

It receives the following arguments

Name Type Default Required Description
comment string - true The comment text.
author string - true Author name that will be shown with the comment.
email string - true The email address of the author.
url string “” false URL of the author’s website.
postId number - true ID of the post where this comment will be posted.
parentId number 0 false ID of the comment parent (if this comment is a reply).

Description

Under the hood, this action would send a POST request to the PHP endpoint used to create comments with data in form-urlencoded format.

To create the URL for that endpoint, the action should get state.source.api and replace /wp-json by /wp-comments-post.php.

Note that this action won’t be supported by wp.com sites. It would be a good idea to detect that case and show a warning message.

The implementation could be something like this:

// Generate form content.
const body = new URLSearchParams();
body.set("comment", comment);
body.set("author", author);
body.set("email", email);
body.set("url", url);
body.set("comment_post_ID", postId);
body.set("comment_parent", parentId);

// Generate endpoint URL.
const commentsPost = state.source.api.replace(/\/wp-json\/?$/, "/wp-comments-post.php")

// Send a POST request.
await fetch(commentsPost, {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" }
  body
});

libraries.source.handlers[commentHandler]

This handler should fetch all comments that belongs to a specific post.

It will work like any other handler so you can use actions.source.fetch to get comments from the REST API and add them to the Frontity state.

In this implementation, all the comments for that post will be fetched.
This means that, if there are more than 100 comments (that’s the maximun value for per_page), more requests should be done.
We can use total and totalPages response headers for that.

A possible implementation is already done in this PR: frontity/frontity#225

Example

Doing the following

actions.source.fetch("@comments/13")
const data = state.source.get("@comments/13");

you will get a data structure similar to this:

data = {
  post: 13,
  areComments: true,
  items: [
    {  type: "comment", id: 2 },
    {  type: "comment", id: 3 },
    {  type: "comment", id: 4, children: [
      { type: "comment", id: 5 },
      { type: "comment", id: 6 },
      { type: "comment", id: 7, children: [
        { type: "comment", id: 8 },
        { type: "comment", id: 9 },
      ] },
    ] },
    {  type: "comment", id: 10 },
    {  type: "comment", id: 11 },
  ]
}

Comments are populated in state.source.comment by id.
You can later iterate over data.items and get the comments from there.

data.items.map(({ id, children }) => {
  // You can iterate over children as well.
  if (children) ... ;
  // Return the comment stored in the state.
  return state.source.comment[id];
})

:warning: Important note

wp-source package uses schemas to know what kind of entities it receives from the REST API and where it should be stored.

Right now it doesn’t support comments and so we should either add a new schema for comments in that package or make schemas extensible so any package can add their own schemas (schemas are being deprecated in the next version of wp-source so it doesn’t make sense to make them extensible).

Possible issues

  1. Add action to create new comments. #439
  2. Add handler to fetch the state with the comments. #440
2 Likes

Let’s go with the hardcoded schema because I want to remove schemas in source v2, and it wouldn’t make much sense to add a system to make schemas extensible that we are going to deprecated.