Merge pull request #4 from mexon/threaded-comments

Threaded comments
This commit is contained in:
Django 2023-01-29 22:07:32 -07:00 committed by GitHub
commit 9cab6248e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 169 additions and 17 deletions

View file

@ -320,3 +320,44 @@ function url_to_authorid( $url ) {
return 0; return 0;
} }
/**
* Examine a comment ID and look up an existing comment it represents.
*
* @param string $id ActivityPub object ID (usually a URL) to check.
*
* @return WP_Comment, or undef if no comment could be found.
*/
function object_id_to_comment( $id ) {
$comment_query = new \WP_Comment_Query(
array(
'meta_key' => 'source_id',
'meta_value' => $id,
)
);
if ( ! $comment_query->comments ) {
return;
}
if ( count( $comment_query->comments ) > 1 ) {
\error_log( "More than one comment under {$id}" );
return;
}
return $comment_query->comments[0];
}
/**
* Examine an activity object and find the post that the specified URL field refers to.
*
* @param string $field_name The name of the URL field in the object to query.
*
* @return int Post ID, or null on failure.
*/
function object_to_post_id_by_field_name( $object, $field_name ) {
if ( ! isset( $object['object'][ $field_name ] ) ) {
return;
}
$result = \url_to_postid( $object['object'][ $field_name ] );
if ( $result > 0 ) {
return $result;
}
}

View file

@ -425,45 +425,85 @@ class Inbox {
} }
/** /**
* Handles "Create" requests * Converts a new ActivityPub object to comment data suitable for creating a comment
* *
* @param array $object The activity-object * @param array $object The activity-object.
* @param int $user_id The id of the local blog-user *
* @return array Comment data suitable for creating a comment.
*/ */
public static function handle_create( $object, $user_id ) { public static function convert_object_to_comment_data( $object ) {
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); if ( ! isset( $object['object'] ) ) {
return false;
if ( ! isset( $object['object']['inReplyTo'] ) ) {
return;
} }
// check if Activity is public or not // check if Activity is public or not
if ( ! self::is_activity_public( $object ) ) { if ( ! self::is_activity_public( $object ) ) {
// @todo maybe send email // @todo maybe send email
return;
}
$comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
// save only replys and reactions
if ( ! $comment_post_id ) {
return false; return false;
} }
$commentdata = array( $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
// Objects must have IDs
if ( ! isset( $object['object']['id'] ) ) {
\error_log( 'Comment provided without ID' );
return;
}
$id = $object['object']['id'];
// Only handle replies
if ( ! isset( $object['object']['inReplyTo'] ) ) {
return;
}
$in_reply_to = $object['object']['inReplyTo'];
// Comment already exists
if ( \Activitypub\object_id_to_comment( $id ) ) {
return;
}
$parent_comment = \Activitypub\object_id_to_comment( $in_reply_to );
// save only replies and reactions
$comment_post_id = \Activitypub\object_to_post_id_by_field_name( $object, 'context' );
if ( ! $comment_post_id ) {
$comment_post_id = \Activitypub\object_to_post_id_by_field_name( $object, 'inReplyTo' );
}
if ( ! $comment_post_id ) {
$comment_post_id = $parent_comment->comment_post_ID;
}
if ( ! $comment_post_id ) {
return;
}
return array(
'comment_post_ID' => $comment_post_id, 'comment_post_ID' => $comment_post_id,
'comment_author' => \esc_attr( $meta['name'] ), 'comment_author' => \esc_attr( $meta['name'] ),
'comment_author_url' => \esc_url_raw( $object['actor'] ), 'comment_author_url' => \esc_url_raw( $object['actor'] ),
'comment_content' => \wp_filter_kses( $object['object']['content'] ), 'comment_content' => \wp_filter_kses( $object['object']['content'] ),
'comment_type' => '', 'comment_type' => '',
'comment_author_email' => '', 'comment_author_email' => '',
'comment_parent' => 0, 'comment_parent' => $parent_comment ? $parent_comment->comment_ID : 0,
'comment_meta' => array( 'comment_meta' => array(
'source_id' => \esc_url_raw( $id ),
'source_url' => \esc_url_raw( $object['object']['url'] ), 'source_url' => \esc_url_raw( $object['object']['url'] ),
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ), 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
'protocol' => 'activitypub', 'protocol' => 'activitypub',
), ),
); );
}
/**
* Handles "Create" requests
*
* @param array $object The activity-object
* @param int $user_id The id of the local blog-user
*/
public static function handle_create( $object, $user_id ) {
$commentdata = self::convert_object_to_comment_data( $object );
if ( ! $commentdata ) {
return false;
}
// disable flood control // disable flood control
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 ); \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
@ -472,6 +512,7 @@ class Inbox {
\add_filter( 'pre_option_require_name_email', '__return_false' ); \add_filter( 'pre_option_require_name_email', '__return_false' );
$state = \wp_new_comment( $commentdata, true ); $state = \wp_new_comment( $commentdata, true );
// TODO: search for comments with that source url as their parent url and update their parent
\remove_filter( 'pre_option_require_name_email', '__return_false' ); \remove_filter( 'pre_option_require_name_email', '__return_false' );

View file

@ -0,0 +1,70 @@
<?php
class Test_Inbox extends WP_UnitTestCase {
#[\ReturnTypeWillChange]
var $post_permalink;
var $user_url;
public function setUp() {
$authordata = \get_userdata( 1 );
$this->user_url = $authordata->user_url;
$post = \wp_insert_post(
array(
'post_author' => 1,
'post_content' => 'test',
)
);
$this->post_permalink = \get_permalink( $post );
\add_filter( 'pre_get_remote_metadata_by_actor', array( '\Test_Inbox', 'get_remote_metadata_by_actor' ), 10, 2);
}
public static function get_remote_metadata_by_actor( $value, $actor ) {
return array(
"name" => "Example User",
"icon" => array(
"url" => "https://example.com/icon",
),
);
}
public function test_convert_object_to_comment_data_basic() {
$inbox = new \Activitypub\Rest\Inbox();
$object = array(
"actor" => $this->user_url,
"to" => [ $this->user_url ],
"cc" => [ "https://www.w3.org/ns/activitystreams#Public" ],
"object" => array(
"id" => "123",
"url" => "https://example.com/example",
"inReplyTo" => $this->post_permalink,
"content" => "example",
),
);
$converted = $inbox->convert_object_to_comment_data($object);
$this->assertGreaterThan(1, $converted["comment_post_ID"]);
$this->assertEquals($converted["comment_author"], "Example User");
$this->assertEquals($converted["comment_author_url"], "http://example.org");
$this->assertEquals($converted["comment_content"], "example");
$this->assertEquals($converted["comment_type"], "");
$this->assertEquals($converted["comment_author_email"], "");
$this->assertEquals($converted["comment_parent"], 0);
$this->assertArrayHasKey("comment_meta", $converted);
$this->assertEquals($converted["comment_meta"]["source_id"], "http://123");
$this->assertEquals($converted["comment_meta"]["source_url"], "https://example.com/example");
$this->assertEquals($converted["comment_meta"]["avatar_url"], "https://example.com/icon");
$this->assertEquals($converted["comment_meta"]["protocol"], "activitypub");
}
public function test_convert_object_to_comment_data_non_public_rejected() {
$inbox = new \Activitypub\Rest\Inbox();
$object = array(
"to" => ["https://example.com/profile/test"],
"cc" => [],
);
$converted = $inbox->convert_object_to_comment_data($object);
$this->assertFalse($converted);
}
}