Refactoring
Some checks failed
PHP Code Checker / PHP Code Checker (pull_request) Failing after 47s
PHPUnit / PHPUnit – PHP 7.4 (pull_request) Failing after 52s
PHPUnit / PHPUnit – PHP 8.0 (pull_request) Failing after 54s
PHPUnit / PHPUnit – PHP 8.1 (pull_request) Failing after 56s
PHPUnit / PHPUnit – PHP 8.2 (pull_request) Failing after 53s
PHPUnit / PHPUnit – PHP 8.3 (pull_request) Failing after 52s
PHPUnit / PHPUnit – PHP 8.4 (pull_request) Failing after 51s

* Use WP_Post ID of the event source for linking the event source with an cached remote event post
* Improve docs
* Add tests for retrieving the event_sources list
This commit is contained in:
André Menrath 2025-01-04 12:17:58 +01:00
parent 8026ea69f4
commit ddeb5c6cca
9 changed files with 190 additions and 110 deletions

View file

@ -216,7 +216,6 @@ class Event_Sources {
public static function delete_event_source_transients(): void {
delete_transient( 'event_bridge_for_activitypub_event_sources' );
delete_transient( 'event_bridge_for_activitypub_event_sources_hosts' );
delete_transient( 'event_bridge_for_activitypub_event_sources_ids' );
}
/**
@ -252,16 +251,16 @@ class Event_Sources {
/**
* Delete all posts of an event source.
*
* @param string $event_source_id The ActivityPub ID of the event source.
* @param int $event_source_post_id The WordPress Post ID of the event source.
* @return void
*/
public static function delete_events_by_event_source( $event_source_id ) {
public static function delete_events_by_event_source( $event_source_post_id ) {
global $wpdb;
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",
'_event_bridge_for_activitypub_event_source',
esc_sql( $event_source_id )
absint( $event_source_post_id )
)
);
@ -271,13 +270,13 @@ class Event_Sources {
}
// Loop through the posts and delete them permanently.
foreach ( $results as $post ) {
foreach ( $results as $result ) {
// Check if the post has a thumbnail.
$thumbnail_id = get_post_thumbnail_id( $post->post_id );
$thumbnail_id = get_post_thumbnail_id( $result->post_id );
if ( $thumbnail_id ) {
// Remove the thumbnail from the post.
\delete_post_thumbnail( $post->post_id );
\delete_post_thumbnail( $result->post_id );
// Delete the attachment (and its files) from the media library.
if ( self::is_attachment_featured_image( $thumbnail_id ) ) {
@ -285,7 +284,7 @@ class Event_Sources {
}
}
\wp_delete_post( $post->post_id, true );
\wp_delete_post( $result->post_id, true );
}
// Clean up the query.
@ -295,26 +294,26 @@ class Event_Sources {
/**
* Remove an Event Source (=Followed ActivityPub actor).
*
* @param string $actor The Actor URL.
* @param string $activitypub_id The Events Sources ActivityPub Actor ID/URL.
*
* @return WP_Post|false|null Post data on success, false or null on failure.
*/
public static function remove_event_source( $actor ) {
public static function remove_event_source( $activitypub_id ) {
self::delete_event_source_transients();
$actor = Event_Source::get_by_id( $actor );
$event_source = Event_Source::get_by_id( $activitypub_id );
if ( ! $actor ) {
if ( ! $event_source ) {
return;
}
$post_id = $actor->get__id();
$post_id = $event_source->get__id();
if ( ! $post_id ) {
return;
}
self::delete_events_by_event_source( $actor->get_id() );
self::delete_events_by_event_source( $post_id);
$thumbnail_id = get_post_thumbnail_id( $post_id );
@ -326,7 +325,7 @@ class Event_Sources {
// If the deletion was successful delete all transients regarding event sources.
if ( $result ) {
self::queue_unfollow_actor( $actor );
self::queue_unfollow_actor( $activitypub_id );
}
return $result;
@ -374,12 +373,11 @@ class Event_Sources {
$args = wp_parse_args( $args, $defaults );
$query = new WP_Query( $args );
$total = $query->found_posts;
$actors = array_map(
function ( $post ) {
return Event_Source::init_from_cpt( $post );
},
$query->get_posts()
);
$actors = array();
foreach ( $query->get_posts() as $post ) {
$actors[ $post->guid ] = Event_Source::init_from_cpt( $post );
}
$event_sources = compact( 'actors', 'total' );

View file

@ -11,7 +11,6 @@ namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
use Activitypub\Collection\Actors;
use Activitypub\Model\Blog;
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
use Event_Bridge_For_ActivityPub\Event_Sources;
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
use function Activitypub\object_to_uri;
@ -45,7 +44,8 @@ class Accept {
}
// Check that we are actually following/or have a pending follow request this actor.
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
$event_source = Event_Source::get_by_id( $activity['actor'] );
if ( ! $event_source ) {
return;
}
@ -53,20 +53,28 @@ class Accept {
$application = new Blog();
$follow_id = Event_Sources_Collection::compose_follow_id( $application->get_id(), $activity['actor'] );
if ( object_to_uri( $activity['object'] ) === $follow_id ) {
$post_id = Event_Source::get_by_id( $activity['actor'] )->get__id();
if ( ! $post_id ) {
return;
}
\update_post_meta( $post_id, '_event_bridge_for_activitypub_accept_of_follow', $activity['id'] );
\wp_update_post(
array(
'ID' => $post_id,
'post_status' => 'publish',
)
);
// Check if the object of the `Accept` is indeed the `Follow` request we sent to that actor.
if ( object_to_uri( $activity['object'] ) !== $follow_id ) {
return;
}
// Get the WordPress post ID of the Event Source. This should not be able to fail here.
$post_id = $event_source->get__id();
if ( ! $post_id ) {
return;
}
// Save the accept status of the follow request to the event source post.
\update_post_meta( $post_id, '_event_bridge_for_activitypub_accept_of_follow', $activity['id'] );
\wp_update_post(
array(
'ID' => $post_id,
'post_status' => 'publish',
)
);
// Trigger the backfilling of events from this actor.
\do_action( 'event_bridge_for_activitypub_backfill_events', $activity['actor'] );
}
}

View file

@ -8,6 +8,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
use Activitypub\Collection\Actors;
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
use Event_Bridge_For_ActivityPub\Event_Sources;
use Event_Bridge_For_ActivityPub\Setup;
@ -51,9 +52,10 @@ class Create {
return;
}
// Check that we are actually following this actor.
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
return false;
// Check that we are actually following/or have a pending follow request this actor.
$event_source = Event_Source::get_by_id( $activity['actor'] );
if ( ! $event_source ) {
return;
}
if ( Event_Sources::is_time_passed( $activity['object']['startTime'] ) ) {
@ -70,6 +72,6 @@ class Create {
return;
}
$transmogrifier->save( $activity['object'], $activity['actor'] );
$transmogrifier->save( $activity['object'], $event_source );
}
}

View file

@ -47,30 +47,24 @@ class Undo {
return;
}
$id = object_to_uri( $activity['object'] );
$accept_id = object_to_uri( $activity['object'] );
// This is what the ID of the follow request would look like.
$args = array(
'post_type' => Event_Sources_Collection::POST_TYPE,
'meta_key' => '_event_bridge_for_activitypub_accept_of_follow',
'meta_query' => array(
array(
'key' => '_event_bridge_for_activitypub_accept_of_follow',
'value' => $id,
'compare' => '=',
),
),
global $wpdb;
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",
'_event_bridge_for_activitypub_accept_of_follow',
esc_sql( $accept_id )
)
);
$query = new \WP_Query( $args );
// If no event source with that accept ID is found return.
if ( ! $query->have_posts() ) {
if ( empty( $results ) || ! $results ) {
return;
}
$post = $query->get_posts()[0];
$post_id = is_a( $post, 'WP_Post' ) ? $post->ID : $post;
$post_id = reset( $results )->post_id;
\delete_post_meta( $post_id, '_event_bridge_for_activitypub_accept_of_follow' );
}

View file

@ -8,6 +8,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
use Activitypub\Collection\Actors;
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
use Event_Bridge_For_ActivityPub\Event_Sources;
use Event_Bridge_For_ActivityPub\Setup;
@ -51,9 +52,10 @@ class Update {
return;
}
// Check that we are actually following this actor.
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
return false;
// Check that we are actually following/or have a pending follow request this actor.
$event_source = Event_Source::get_by_id( $activity['actor'] );
if ( ! $event_source ) {
return;
}
if ( Event_Sources::is_time_passed( $activity['object']['startTime'] ) ) {
@ -66,6 +68,6 @@ class Update {
return;
}
$transmogrifier->save( $activity['object'], $activity['actor'] );
$transmogrifier->save( $activity['object'], $event_source );
}
}

View file

@ -122,16 +122,17 @@ class Event_Source extends Actor {
/**
* Get the Event Source by the ActivityPub ID.
*
* @param string $actor_id The ActivityPub actor ID.
* @return Event_Source|WP_Error|false The Event Source Actor, if a WordPress Post representing it is found, false otherwise.
* @param string $activitypub_actor_id The ActivityPub actor ID.
* @return Event_Source|false The Event Source Actor, if a WordPress Post representing it is found, false otherwise.
*/
public static function get_by_id( $actor_id ) {
$post = self::get_wp_post_by_activitypub_actor_id( $actor_id );
public static function get_by_id( $activitypub_actor_id ) {
$event_sources = Event_Sources::get_event_sources();
if ( $post ) {
$actor = self::init_from_cpt( $post );
if ( ! array_key_exists( $activitypub_actor_id, $event_sources ) ) {
return false;
}
return $actor ?? false;
return $event_sources[ $activitypub_actor_id ];
}
/**

View file

@ -43,10 +43,10 @@ abstract class Base {
/**
* Save the ActivityPub event object within WordPress.
*
* @param array $activitypub_event The ActivityPub event as associative array.
* @param ?string $actor The ActivityPub ID of the actor which we received the event from.
* @param array $activitypub_event The ActivityPub event as associative array.
* @param Event_Source $event_source The Event Source we received the event from.
*/
public function save( $activitypub_event, $actor ) {
public function save( $activitypub_event, $event_source ) {
$activitypub_event = Event::init_from_array( $activitypub_event );
if ( is_wp_error( $activitypub_event ) ) {
@ -57,20 +57,23 @@ abstract class Base {
$post_id = $this->save_event();
$event_id = $activitypub_event['id'];
$event_id = $activitypub_event->get_id();
$event_source_activitypub_id = $event_source->get_id();
$event_source_post_id = $event_source->get__id();
if ( $post_id ) {
\do_action(
'event_bridge_for_activitypub_write_log',
array( "[ACTIVITYPUB] Processed incoming event {$event_id} from {$actor}" )
array( "[ACTIVITYPUB] Processed incoming event {$event_id} from {$event_source_activitypub_id}" )
);
update_post_meta( $post_id, '_event_bridge_for_activitypub_is_remote_cached', true );
update_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', sanitize_url( $actor ) );
update_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', absint( $event_source_post_id ) );
update_post_meta( $post_id, 'activitypub_content_visibility', constant( 'ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL' ) ?? '' );
} else {
\do_action(
'event_bridge_for_activitypub_write_log',
array( "[ACTIVITYPUB] Failed processing incoming event {$event_id} from {$actor}" )
array( "[ACTIVITYPUB] Failed processing incoming event {$event_id} from {$event_source_activitypub_id}" )
);
}
}

View file

@ -119,13 +119,11 @@ class Event_Sources {
\register_post_meta(
$event_plugin_integration::get_post_type(),
'_event_bridge_for_activitypub_attributed_to',
'_event_bridge_for_activitypub_event_source',
array(
'type' => 'string',
'single' => false,
'sanitize_callback' => function ( $value ) {
return sanitize_url( $value );
},
'type' => 'integer',
'single' => true,
'sanitize_callback' => 'absint',
)
);
}
@ -283,7 +281,7 @@ class Event_Sources {
return $follow_list;
}
$event_sources = self::get_event_sources_ids();
$event_sources = array_keys( Event_Sources_Collection::get_event_sources() );
if ( ! is_array( $event_sources ) ) {
return $follow_list;
@ -304,11 +302,11 @@ class Event_Sources {
return $hosts;
}
$actors = Event_Sources_Collection::get_event_sources();
$event_sources = Event_Sources_Collection::get_event_sources();
$hosts = array();
foreach ( $actors as $actor ) {
$url = wp_parse_url( $actor->get_id() );
foreach ( array_keys( $event_sources ) as $actor ) {
$url = wp_parse_url( $actor );
if ( isset( $url['host'] ) ) {
$hosts[] = $url['host'];
}
@ -321,30 +319,6 @@ class Event_Sources {
return $hosts;
}
/**
* Get add Event Sources ActivityPub IDs.
*
* @return array A list with the ActivityPub IDs of all Event Sources (follows).
*/
public static function get_event_sources_ids() {
$ids = get_transient( 'event_bridge_for_activitypub_event_sources_ids' );
if ( $ids ) {
return $ids;
}
$actors = Event_Sources_Collection::get_event_sources();
$ids = array();
foreach ( $actors as $actor ) {
$ids[] = $actor->get_id();
}
set_transient( 'event_bridge_for_activitypub_event_sources_ids', $ids );
return $ids;
}
/**
* Add Event Sources hosts to allowed hosts used by safe redirect.
*
@ -511,7 +485,7 @@ class Event_Sources {
* @return bool True if the ActivityPub actor ID is followed, false otherwise.
*/
public static function actor_is_event_source( $actor_id ) {
$event_sources_ids = self::get_event_sources_ids();
$event_sources_ids = array_keys( Event_Sources_Collection::get_event_sources() );
if ( in_array( $actor_id, $event_sources_ids, true ) ) {
return true;
}

View file

@ -0,0 +1,98 @@
<?php
/**
* Test file for the Event Sources collection.
*
* @package Event_Bridge_For_ActivityPub
* @since 1.0.0
* @license AGPL-3.0-or-later
*/
namespace Event_Bridge_For_ActivityPub\Tests\ActivityPub\Collection;
use WP_REST_Server;
/**
* Test class for the Event Sources collection.
*
* @coversDefaultClass \Event_Bridge_For_ActivityPub\ActivityPub\Collections\Event_Sources
*/
class Test_Event_Sources extends \WP_UnitTestCase {
const FOLLOWED_ACTOR_1 = array(
'id' => 'https://remote.example/@organizer',
'type' => 'Person',
'inbox' => 'https://remote.example/@organizer/inbox',
'outbox' => 'https://remote.example/@organizer/outbox',
'name' => 'The Organizer',
'summary' => 'Just a random organizer of events in the Fediverse',
);
const FOLLOWED_ACTOR_2 = array(
'id' => 'https://remote.example/@organizer2',
'type' => 'Person',
'inbox' => 'https://remote.example/@organizer2/inbox',
'outbox' => 'https://remote.example/@organizer2/outbox',
'name' => 'The Second Organizer',
'summary' => 'Just a second random organizer of events in the Fediverse',
);
/**
* REST Server.
*
* @var WP_REST_Server
*/
protected $server;
/**
* Set up the test.
*/
public function set_up() {
if ( ! defined( 'GATHERPRESS_CORE_FILE' ) ) {
self::markTestSkipped( 'GatherPress plugin is not active.' );
}
\add_option( 'permalink_structure', '/%postname%/' );
global $wp_rest_server;
$wp_rest_server = new WP_REST_Server();
$this->server = $wp_rest_server;
do_action( 'rest_api_init' );
\Activitypub\Rest\Server::add_hooks();
// Mock the plugin activation.
\GatherPress\Core\Setup::get_instance()->activate_gatherpress_plugin( false );
// Make sure that ActivityPub support is enabled for GatherPress.
$aec = \Event_Bridge_For_ActivityPub\Setup::get_instance();
$aec->activate_activitypub_support_for_active_event_plugins();
// Add event sources (ActivityPub followers).
_delete_all_posts();
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR_1 )->save();
\Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source::init_from_array( self::FOLLOWED_ACTOR_2 )->save();
\update_option( 'event_bridge_for_activitypub_event_sources_active', true );
\update_option(
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class
);
\update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
}
/**
* Testing the fetching of event sources from the database.
*
* @covers \Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources::get_event_sources_with_count
* @covers \Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources::get_event_sources
*/
public function test_get_event_sources_with_count() {
\delete_transient( 'event_bridge_for_activitypub_event_sources' );
$event_sources = \Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources::get_event_sources();
$this->assertCount( 2, $event_sources );
$this->assertArrayHasKey( self::FOLLOWED_ACTOR_1['id'], $event_sources );
$this->assertArrayHasKey( self::FOLLOWED_ACTOR_2['id'], $event_sources );
}
}