Skip to content

Commit eb96988

Browse files
pfefferleobenland
andauthored
Add basic relay support! (#1291)
* Combine sanitization functions * Add host_list sanitization * add settings * Add relay settings * Add settings * Add tests * fixed phpcs * Added changelog * Fix PHPCS * check if relay is empty * use sanitize class * use a more generic filter and update the relays feature to use the filter * add `code` to allowlist props @obenland * remove duplicate code props @obenland * move description below textbox props @obenland * Fix phpcs * rename filter * rename function * fix phpcs * added changelog --------- Co-authored-by: Konstantin Obenland <[email protected]>
1 parent 1ea3c08 commit eb96988

File tree

6 files changed

+186
-50
lines changed

6 files changed

+186
-50
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Support for sending Activities to ActivityPub Relays, to improve discoverability of public content.

includes/class-dispatcher.php

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,13 @@ public static function init() {
4949
\add_action( 'activitypub_process_outbox', array( self::class, 'process_outbox' ) );
5050

5151
// Default filters to add Inboxes to sent to.
52-
\add_filter( 'activitypub_interactees_inboxes', array( self::class, 'add_inboxes_by_mentioned_actors' ), 10, 3 );
53-
\add_filter( 'activitypub_interactees_inboxes', array( self::class, 'add_inboxes_of_replied_urls' ), 10, 3 );
52+
\add_filter( 'activitypub_additional_inboxes', array( self::class, 'add_inboxes_by_mentioned_actors' ), 10, 3 );
53+
\add_filter( 'activitypub_additional_inboxes', array( self::class, 'add_inboxes_of_replied_urls' ), 10, 3 );
54+
\add_filter( 'activitypub_additional_inboxes', array( self::class, 'add_inboxes_of_relays' ), 10, 3 );
5455

5556
// Fallback for `activitypub_send_to_inboxes` filter.
5657
\add_filter(
57-
'activitypub_interactees_inboxes',
58+
'activitypub_additional_inboxes',
5859
function ( $inboxes, $actor_id, $activity ) {
5960
/**
6061
* Filters the list of interactees inboxes to send the Activity to.
@@ -63,9 +64,13 @@ function ( $inboxes, $actor_id, $activity ) {
6364
* @param int $actor_id The actor ID.
6465
* @param Activity $activity The ActivityPub Activity.
6566
*
66-
* @deprecated 5.2.0 Use `activitypub_interactees_inboxes` instead.
67+
* @deprecated 5.2.0 Use `activitypub_additional_inboxes` instead.
68+
* @deprecated 5.4.0 Use `activitypub_additional_inboxes` instead.
6769
*/
68-
return \apply_filters_deprecated( 'activitypub_send_to_inboxes', array( $inboxes, $actor_id, $activity ), '5.2.0', 'activitypub_interactees_inboxes' );
70+
$inboxes = \apply_filters_deprecated( 'activitypub_send_to_inboxes', array( $inboxes, $actor_id, $activity ), '5.2.0', 'activitypub_additional_inboxes' );
71+
$inboxes = \apply_filters_deprecated( 'activitypub_interactees_inboxes', array( $inboxes, $actor_id, $activity ), '5.4.0', 'activitypub_additional_inboxes' );
72+
73+
return $inboxes;
6974
},
7075
10,
7176
3
@@ -95,7 +100,7 @@ public static function process_outbox( $id ) {
95100
$activity = Outbox::get_activity( $outbox_item );
96101

97102
// Send to mentioned and replied-to users. Everyone other than followers.
98-
self::send_to_interactees( $activity, $actor->get__id(), $outbox_item );
103+
self::send_to_additional_inboxes( $activity, $actor->get__id(), $outbox_item );
99104

100105
if ( self::should_send_to_followers( $activity, $actor, $outbox_item ) ) {
101106
Scheduler::async_batch(
@@ -250,21 +255,23 @@ private static function schedule_retry( $retries, $outbox_item_id, $attempt = 1
250255
}
251256

252257
/**
253-
* Send an Activity to all followers and mentioned users.
258+
* Send an Activity to a custom list of inboxes, like mentioned users or replied-to posts.
259+
*
260+
* For all custom implementations, please use the `activitypub_additional_inboxes` filter.
254261
*
255262
* @param Activity $activity The ActivityPub Activity.
256263
* @param int $actor_id The actor ID.
257264
* @param \WP_Post $outbox_item The WordPress object.
258265
*/
259-
private static function send_to_interactees( $activity, $actor_id, $outbox_item = null ) {
266+
private static function send_to_additional_inboxes( $activity, $actor_id, $outbox_item = null ) {
260267
/**
261268
* Filters the list of inboxes to send the Activity to.
262269
*
263270
* @param array $inboxes The list of inboxes to send to.
264271
* @param int $actor_id The actor ID.
265272
* @param Activity $activity The ActivityPub Activity.
266273
*/
267-
$inboxes = apply_filters( 'activitypub_interactees_inboxes', array(), $actor_id, $activity );
274+
$inboxes = apply_filters( 'activitypub_additional_inboxes', array(), $actor_id, $activity );
268275
$inboxes = array_unique( $inboxes );
269276

270277
$retries = self::send_to_inboxes( $inboxes, $outbox_item->ID );
@@ -412,4 +419,34 @@ protected static function should_send_to_followers( $activity, $actor, $outbox_i
412419
*/
413420
return apply_filters( 'activitypub_send_activity_to_followers', $send, $activity, $actor->get__id(), $outbox_item );
414421
}
422+
423+
/**
424+
* Add Inboxes of Relays.
425+
*
426+
* @param array $inboxes The list of Inboxes.
427+
* @param int $actor_id The Actor-ID.
428+
* @param Activity $activity The ActivityPub Activity.
429+
*
430+
* @return array The filtered Inboxes.
431+
*/
432+
public static function add_inboxes_of_relays( $inboxes, $actor_id, $activity ) {
433+
// Check if follower endpoint is set.
434+
$cc = $activity->get_cc() ?? array();
435+
$to = $activity->get_to() ?? array();
436+
437+
$audience = array_merge( $cc, $to );
438+
439+
// Check if activity is public.
440+
if ( ! in_array( 'https://www.w3.org/ns/activitystreams#Public', $audience, true ) ) {
441+
return $inboxes;
442+
}
443+
444+
$relays = \get_option( 'activitypub_relays', array() );
445+
446+
if ( empty( $relays ) ) {
447+
return $inboxes;
448+
}
449+
450+
return array_merge( $inboxes, $relays );
451+
}
415452
}

includes/wp-admin/class-settings-fields.php

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ public static function register_settings_fields() {
5151
'activitypub_settings'
5252
);
5353

54+
add_settings_section(
55+
'activitypub_server',
56+
__( 'Server', 'activitypub' ),
57+
'__return_empty_string',
58+
'activitypub_settings'
59+
);
60+
5461
// Add settings fields.
5562
add_settings_field(
5663
'activitypub_actor_mode',
@@ -129,15 +136,24 @@ public static function register_settings_fields() {
129136
__( 'Blocklist', 'activitypub' ),
130137
array( self::class, 'render_blocklist_field' ),
131138
'activitypub_settings',
132-
'activitypub_general'
139+
'activitypub_server'
140+
);
141+
142+
add_settings_field(
143+
'activitypub_relays',
144+
__( 'Relays', 'activitypub' ),
145+
array( self::class, 'render_relays_field' ),
146+
'activitypub_settings',
147+
'activitypub_server',
148+
array( 'label_for' => 'activitypub_relays' )
133149
);
134150

135151
add_settings_field(
136152
'activitypub_outbox_purge_days',
137153
__( 'Outbox Retention Period', 'activitypub' ),
138154
array( self::class, 'render_outbox_purge_days_field' ),
139155
'activitypub_settings',
140-
'activitypub_general',
156+
'activitypub_server',
141157
array( 'label_for' => 'activitypub_outbox_purge_days' )
142158
);
143159

@@ -466,4 +482,35 @@ public static function render_authorized_fetch_field() {
466482
</p>
467483
<?php
468484
}
485+
486+
/**
487+
* Render relays field.
488+
*/
489+
public static function render_relays_field() {
490+
$value = get_option( 'activitypub_relays', array() );
491+
?>
492+
<textarea
493+
id="activitypub_relays"
494+
name="activitypub_relays"
495+
class="large-text"
496+
cols="50"
497+
rows="5"
498+
><?php echo esc_textarea( implode( PHP_EOL, $value ) ); ?></textarea>
499+
<p class="description">
500+
<?php echo wp_kses( __( 'A <strong>Fediverse-Relay</strong> distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?>
501+
</p>
502+
<p class="description">
503+
<?php
504+
echo wp_kses(
505+
__( 'Enter the <strong>Inbox-URLs</strong> (e.g. <code>https://relay.example.com/inbox</code>) of the relays you want to use, one per line.', 'activitypub' ),
506+
array(
507+
'strong' => array(),
508+
'code' => array(),
509+
)
510+
);
511+
?>
512+
<?php echo wp_kses( __( 'You can find a list of public relays on <a href="https://relaylist.com/" target="_blank">relaylist.com</a> or on <a href="https://fedidb.org/software/activity-relay" target="_blank">FediDB</a>.', 'activitypub' ), 'default' ); ?>
513+
</p>
514+
<?php
515+
}
469516
}

includes/wp-admin/class-settings.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,17 @@ public static function register_settings() {
171171
)
172172
);
173173

174+
\register_setting(
175+
'activitypub',
176+
'activitypub_relays',
177+
array(
178+
'type' => 'array',
179+
'description' => \__( 'Relays', 'activitypub' ),
180+
'default' => array(),
181+
'sanitize_callback' => array( Sanitize::class, 'url_list' ),
182+
)
183+
);
184+
174185
// Blog-User Settings.
175186
\register_setting(
176187
'activitypub_blog',

integration/class-stream-connector.php

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -129,41 +129,6 @@ public function callback_activitypub_notification_follow( $notification ) {
129129
);
130130
}
131131

132-
/**
133-
* Callback for activitypub_send_to_inboxes.
134-
*
135-
* @param array $result The result of the remote post request.
136-
* @param string $inbox The inbox URL.
137-
* @param string $json The ActivityPub Activity JSON.
138-
* @param int $actor_id The actor ID.
139-
* @param int $outbox_item_id The Outbox item ID.
140-
*/
141-
public function callback_activitypub_sent_to_inbox( $result, $inbox, $json, $actor_id, $outbox_item_id ) {
142-
if ( ! \is_wp_error( $result ) ) {
143-
return;
144-
}
145-
146-
$outbox_item = \get_post( $outbox_item_id );
147-
$outbox_data = $this->prepare_outbox_data_for_response( $outbox_item );
148-
149-
$this->log(
150-
// translators: 1: post title.
151-
sprintf( __( 'Outbox error for "%1$s"', 'activitypub' ), $outbox_data['title'] ),
152-
array(
153-
'error' => wp_json_encode(
154-
array(
155-
'inbox' => $inbox,
156-
'code' => $result->get_error_code(),
157-
'message' => $result->get_error_message(),
158-
)
159-
),
160-
),
161-
$outbox_data['id'],
162-
$outbox_data['type'],
163-
'processed'
164-
);
165-
}
166-
167132
/**
168133
* Callback for activitypub_outbox_processing_complete.
169134
*

tests/includes/class-test-dispatcher.php

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use Activitypub\Activity\Activity;
99
use Activitypub\Collection\Actors;
10+
use Activitypub\Collection\Outbox;
1011
use Activitypub\Collection\Followers;
1112
use Activitypub\Dispatcher;
1213

@@ -103,22 +104,22 @@ public function test_process_outbox() {
103104
* This test can be removed when the filter is removed.
104105
*
105106
* @covers ::maybe_add_inboxes_of_blog_user
106-
* @expectedDeprecated activitypub_send_to_inboxes
107+
* @expectedDeprecated activitypub_interactees_inboxes
107108
*/
108109
public function test_deprecated_filter() {
109110
add_filter(
110-
'activitypub_send_to_inboxes',
111+
'activitypub_interactees_inboxes',
111112
function ( $inboxes ) {
112113
$inboxes[] = 'https://example.com/inbox';
113114

114115
return $inboxes;
115116
}
116117
);
117118

118-
$inboxes = apply_filters( 'activitypub_interactees_inboxes', array(), 1, $this->get_activity_mock() );
119+
$inboxes = apply_filters( 'activitypub_additional_inboxes', array(), 1, $this->get_activity_mock() );
119120
$this->assertContains( 'https://example.com/inbox', $inboxes );
120121

121-
remove_all_filters( 'activitypub_send_to_inboxes' );
122+
remove_all_filters( 'activitypub_interactees_inboxes' );
122123
}
123124

124125
/**
@@ -171,6 +172,77 @@ function () use ( $code, $message ) {
171172
remove_all_filters( 'pre_http_request' );
172173
}
173174

175+
/**
176+
* Test send_to_additional_inboxes.
177+
*
178+
* @covers ::send_to_additional_inboxes
179+
*/
180+
public function test_send_to_relays() {
181+
global $wp_actions;
182+
183+
$post_id = self::factory()->post->create( array( 'post_author' => self::$user_id ) );
184+
$outbox_item = $this->get_latest_outbox_item( \add_query_arg( 'p', $post_id, \home_url( '/' ) ) );
185+
$fake_request = function () {
186+
return new \WP_Error( 'test', 'test' );
187+
};
188+
189+
add_filter( 'pre_http_request', $fake_request, 10, 3 );
190+
191+
// Make `Dispatcher::send_to_additional_inboxes` a public method.
192+
$send_to_additional_inboxes = new ReflectionMethod( Dispatcher::class, 'send_to_additional_inboxes' );
193+
$send_to_additional_inboxes->setAccessible( true );
194+
195+
$send_to_additional_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item );
196+
197+
// Test how often the request was sent.
198+
$this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) );
199+
200+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
201+
$wp_actions = null;
202+
203+
// Add a relay.
204+
$relays = array( 'https://relay1.example.com/inbox' );
205+
update_option( 'activitypub_relays', $relays );
206+
207+
$send_to_additional_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item );
208+
209+
// Test how often the request was sent.
210+
$this->assertEquals( 1, did_action( 'activitypub_sent_to_inbox' ) );
211+
212+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
213+
$wp_actions = null;
214+
215+
// Add a relay.
216+
$relays = array( 'https://relay1.example.com/inbox', 'https://relay2.example.com/inbox' );
217+
update_option( 'activitypub_relays', $relays );
218+
219+
$send_to_additional_inboxes->invoke( null, $this->get_activity_mock(), Actors::get_by_id( self::$user_id ), $outbox_item );
220+
221+
// Test how often the request was sent.
222+
$this->assertEquals( 2, did_action( 'activitypub_sent_to_inbox' ) );
223+
224+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
225+
$wp_actions = null;
226+
227+
$private_activity = Outbox::get_activity( $outbox_item->ID );
228+
$private_activity->set_to( null );
229+
$private_activity->set_cc( null );
230+
231+
// Clone object.
232+
$private_activity = clone $private_activity;
233+
234+
$send_to_additional_inboxes->invoke( null, $private_activity, Actors::get_by_id( self::$user_id ), $outbox_item );
235+
236+
// Test how often the request was sent.
237+
$this->assertEquals( 0, did_action( 'activitypub_sent_to_inbox' ) );
238+
239+
\remove_filter( 'pre_http_request', $fake_request, 10 );
240+
241+
\delete_option( 'activitypub_relays' );
242+
\wp_delete_post( $post_id );
243+
\wp_delete_post( $outbox_item->ID );
244+
}
245+
174246
/**
175247
* Test whether an activity should be sent to followers.
176248
*

0 commit comments

Comments
 (0)