Skip to content

Commit e6d443e

Browse files
obenlandpfefferle
andauthored
Add webfinger as comment author email if available (#1374)
* Add webfinger as comment author email if available * Add upgrade routine * Add unit tests * Changelog * add doc to reverse-discovery spec --------- Co-authored-by: Matthias Pfefferle <[email protected]>
1 parent 391d9d9 commit e6d443e

File tree

7 files changed

+193
-1
lines changed

7 files changed

+193
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
* Bumped minimum required WordPress version to 6.4.
1313
* Use a later hook for Posts to get published to the Outbox, to get sure all `post_meta`s and `taxonomy`s are set stored properly.
14+
* Use webfinger as author email for comments from the Fediverse.
1415

1516
## [5.3.2] - 2025-02-27
1617

includes/class-migration.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ public static function maybe_migrate() {
185185
if ( \version_compare( $version_from_db, '5.3.0', '<' ) ) {
186186
add_action( 'init', 'flush_rewrite_rules', 20 );
187187
}
188+
if ( \version_compare( $version_from_db, 'unreleased', '<' ) ) {
189+
\wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_comment_author_emails' ) );
190+
}
188191

189192
/*
190193
* Add new update routines above this comment. ^
@@ -632,6 +635,57 @@ public static function create_comment_outbox_items( $batch_size = 50, $offset =
632635
return null;
633636
}
634637

638+
/**
639+
* Update comment author emails with webfinger addresses for ActivityPub comments.
640+
*
641+
* @param int $batch_size Optional. Number of comments to process per batch. Default 50.
642+
* @param int $offset Optional. Number of comments to skip. Default 0.
643+
* @return array|null Array with batch size and offset if there are more comments to process, null otherwise.
644+
*/
645+
public static function update_comment_author_emails( $batch_size = 50, $offset = 0 ) {
646+
$comments = \get_comments(
647+
array(
648+
'number' => $batch_size,
649+
'offset' => $offset,
650+
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
651+
'meta_query' => array(
652+
array(
653+
'key' => 'protocol',
654+
'value' => 'activitypub',
655+
),
656+
),
657+
)
658+
);
659+
660+
foreach ( $comments as $comment ) {
661+
$comment_author_url = $comment->comment_author_url;
662+
if ( empty( $comment_author_url ) ) {
663+
continue;
664+
}
665+
666+
$webfinger = Webfinger::uri_to_acct( $comment_author_url );
667+
if ( \is_wp_error( $webfinger ) ) {
668+
continue;
669+
}
670+
671+
\wp_update_comment(
672+
array(
673+
'comment_ID' => $comment->comment_ID,
674+
'comment_author_email' => \str_replace( 'acct:', '', $webfinger ),
675+
)
676+
);
677+
}
678+
679+
if ( count( $comments ) === $batch_size ) {
680+
return array(
681+
'batch_size' => $batch_size,
682+
'offset' => $offset + $batch_size,
683+
);
684+
}
685+
686+
return null;
687+
}
688+
635689
/**
636690
* Set the defaults needed for the plugin to work.
637691
*

includes/class-webfinger.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public static function resolve( $uri ) {
8484
/**
8585
* Transform a URI to an acct <identifier>@<host>.
8686
*
87+
* @see https://swicg.github.io/activitypub-webfinger/#reverse-discovery
88+
*
8789
* @param string $uri The URI (acct:, mailto:, http:, https:).
8890
*
8991
* @return string|WP_Error Error or acct URI.

includes/collection/class-interactions.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Activitypub\Collection;
99

10+
use Activitypub\Webfinger;
1011
use WP_Comment_Query;
1112
use Activitypub\Comment;
1213

@@ -256,12 +257,19 @@ public static function activity_to_comment( $activity ) {
256257
$comment_content = \addslashes( $activity['object']['content'] );
257258
}
258259

260+
$webfinger = Webfinger::uri_to_acct( $url );
261+
if ( is_wp_error( $webfinger ) ) {
262+
$webfinger = '';
263+
} else {
264+
$webfinger = str_replace( 'acct:', '', $webfinger );
265+
}
266+
259267
$commentdata = array(
260268
'comment_author' => \esc_attr( $comment_author ),
261269
'comment_author_url' => \esc_url_raw( $url ),
262270
'comment_content' => $comment_content,
263271
'comment_type' => 'comment',
264-
'comment_author_email' => '',
272+
'comment_author_email' => $webfinger,
265273
'comment_meta' => array(
266274
'source_id' => \esc_url_raw( object_to_uri( $activity['object'] ) ),
267275
'protocol' => 'activitypub',

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ For reasons of data protection, it is not possible to see the followers of other
133133

134134
* Changed: Bumped minimum required WordPress version to 6.4.
135135
* Changed: Use a later hook for Posts to get published to the Outbox, to get sure all `post_meta`s and `taxonomy`s are set stored properly.
136+
* Changed: Use webfinger as author email for comments from the Fediverse.
136137

137138
= 5.3.2 =
138139

tests/includes/class-test-migration.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,4 +597,99 @@ public function test_create_comment_outbox_items_batching() {
597597
$result = Migration::create_comment_outbox_items( 1, 1000 );
598598
$this->assertNull( $result );
599599
}
600+
601+
/**
602+
* Test update_comment_author_emails updates emails with webfinger addresses.
603+
*
604+
* @covers ::update_comment_author_emails
605+
*/
606+
public function test_update_comment_author_emails() {
607+
$author_url = 'https://example.com/users/test';
608+
$comment_id = self::factory()->comment->create(
609+
array(
610+
'comment_post_ID' => self::$fixtures['posts'][0],
611+
'comment_author' => 'Test User',
612+
'comment_author_url' => $author_url,
613+
'comment_author_email' => '',
614+
'comment_type' => 'comment',
615+
'comment_meta' => array( 'protocol' => 'activitypub' ),
616+
)
617+
);
618+
619+
// Mock the HTTP request.
620+
\add_filter( 'pre_http_request', array( $this, 'mock_webfinger' ) );
621+
622+
$result = Migration::update_comment_author_emails( 50, 0 );
623+
624+
$this->assertNull( $result );
625+
626+
$updated_comment = \get_comment( $comment_id );
627+
$this->assertEquals( '[email protected]', $updated_comment->comment_author_email );
628+
629+
// Clean up.
630+
\remove_filter( 'pre_http_request', array( $this, 'mock_webfinger' ) );
631+
\wp_delete_comment( $comment_id, true );
632+
}
633+
634+
/**
635+
* Test update_comment_author_emails handles batching correctly.
636+
*
637+
* @covers ::update_comment_author_emails
638+
*/
639+
public function test_update_comment_author_emails_batching() {
640+
// Create multiple comments.
641+
$comment_ids = array();
642+
for ( $i = 0; $i < 3; $i++ ) {
643+
$comment_ids[] = self::factory()->comment->create(
644+
array(
645+
'comment_post_ID' => self::$fixtures['posts'][0],
646+
'comment_author' => "Test User $i",
647+
'comment_author_url' => "https://example.com/users/test$i",
648+
'comment_author_email' => '',
649+
'comment_content' => "Test comment $i",
650+
'comment_type' => 'comment',
651+
'comment_meta' => array( 'protocol' => 'activitypub' ),
652+
)
653+
);
654+
}
655+
656+
// Mock the HTTP request.
657+
\add_filter( 'pre_http_request', array( $this, 'mock_webfinger' ) );
658+
659+
// Process first batch of 2 comments.
660+
$result = Migration::update_comment_author_emails( 2, 0 );
661+
$this->assertEqualSets(
662+
array(
663+
'batch_size' => 2,
664+
'offset' => 2,
665+
),
666+
$result
667+
);
668+
669+
// Process second batch with remaining comment.
670+
$result = Migration::update_comment_author_emails( 2, 2 );
671+
$this->assertNull( $result );
672+
673+
// Verify all comments were updated.
674+
foreach ( $comment_ids as $comment_id ) {
675+
$comment = \get_comment( $comment_id );
676+
$this->assertEquals( '[email protected]', $comment->comment_author_email );
677+
678+
wp_delete_comment( $comment_id, true );
679+
}
680+
681+
\remove_filter( 'pre_http_request', array( $this, 'mock_webfinger' ) );
682+
}
683+
684+
/**
685+
* Mock webfinger response.
686+
*
687+
* @return array
688+
*/
689+
public function mock_webfinger() {
690+
return array(
691+
'body' => wp_json_encode( array( 'subject' => 'acct:[email protected]' ) ),
692+
'response' => array( 'code' => 200 ),
693+
);
694+
}
600695
}

tests/includes/collection/class-test-interactions.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,4 +471,35 @@ function () {
471471

472472
remove_all_filters( 'pre_get_remote_metadata_by_actor' );
473473
}
474+
475+
/**
476+
* Test activity_to_comment sets webfinger as comment author email.
477+
*
478+
* @covers ::activity_to_comment
479+
*/
480+
public function test_activity_to_comment_sets_webfinger_email() {
481+
$actor_url = 'https://example.com/users/tester';
482+
$activity = array(
483+
'type' => 'Create',
484+
'actor' => $actor_url,
485+
'object' => array(
486+
'content' => 'Test comment content',
487+
'id' => 'https://example.com/activities/1',
488+
),
489+
);
490+
491+
$filter = function () {
492+
return array(
493+
'body' => wp_json_encode( array( 'subject' => 'acct:[email protected]' ) ),
494+
'response' => array( 'code' => 200 ),
495+
);
496+
};
497+
\add_filter( 'pre_http_request', $filter );
498+
499+
$comment_data = Interactions::activity_to_comment( $activity );
500+
501+
$this->assertEquals( '[email protected]', $comment_data['comment_author_email'] );
502+
503+
\remove_filter( 'pre_http_request', $filter );
504+
}
474505
}

0 commit comments

Comments
 (0)