updated plugin Event Bridge for ActivityPub version 1.3.0

This commit is contained in:
2026-06-03 21:28:57 +00:00
committed by Gitium
parent 1f3438440f
commit f2d6714572
84 changed files with 1721 additions and 1361 deletions

View File

@ -9,7 +9,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Collection\Actors;
use Activitypub\Model\Blog;
@ -37,10 +37,10 @@ class Accept {
/**
* Handle incoming "Accept" activities.
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
* @param array $activity The activity-object.
* @param int|int[] $user_id The id of the local blog-user.
*/
public static function handle_accept( $activity, $user_id ): void {
public static function handle_accept( array $activity, int|array $user_id ): void {
// We only process activities that are target to the blog actor.
if ( Actors::BLOG_USER_ID !== $user_id ) {
return;

View File

@ -8,7 +8,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Collection\Actors;
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
@ -36,10 +36,10 @@ class Create {
/**
* Handle incoming "Create" activities.
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
* @param array $activity The activity-object.
* @param int|int[] $user_id The id of the local blog-user.
*/
public static function handle_create( $activity, $user_id ): void {
public static function handle_create( array $activity, int|array $user_id ): void {
// We only process activities that are target to the blog actor.
if ( Actors::BLOG_USER_ID !== $user_id ) {
return;
@ -56,7 +56,7 @@ class Create {
}
// Check that we are actually following/or have a pending follow request this actor.
$event_source_post_id = Event_Source::get_post_id_by_activitypub_id( $activity['actor'] );
$event_source_post_id = self::determine_event_source( $activity );
if ( ! $event_source_post_id ) {
return;
}
@ -65,6 +65,11 @@ class Create {
return;
}
// Apply custom filters whether an Event should be ignored.
if ( \apply_filters( 'event_bridge_for_activitypub_ignore_incoming_event', false, $activity['object'], $event_source_post_id ) ) {
return;
}
$transmogrifier = Setup::get_transmogrifier();
if ( ! $transmogrifier ) {
@ -73,4 +78,36 @@ class Create {
$transmogrifier::save( $activity['object'], $event_source_post_id );
}
/**
* Lookup the post ID of the event source for a given ActivityPub activity.
*
* First tries the actor of the activity. For Mobilizon-like groups, falls back
* to the attributedTo field if it has the same host as the actor.
*
* @param array $activity The ActivityPub activity as associative array.
* @return int|false Post ID of the event source, or false if none found.
*/
public static function determine_event_source( $activity ): int|false {
$event_source_post_id = Event_Source::get_post_id_by_activitypub_id( $activity['actor'] );
if ( $event_source_post_id ) {
return $event_source_post_id;
}
// Fallback for Mobilizon groups, where a member of the group sends them.
if ( ! isset( $activity['object']['attributedTo'] ) ) {
return false;
}
// We only trust this, when the attributedTo of the event object and the actor of the activity have the same hostname.
$actor_host = \wp_parse_url( $activity['actor'], PHP_URL_HOST );
$attributed_to_host = \wp_parse_url( $activity['object']['attributedTo'], PHP_URL_HOST );
if ( $actor_host === $attributed_to_host ) {
return Event_Source::get_post_id_by_activitypub_id( $activity['object']['attributedTo'] );
}
return false;
}
}

View File

@ -8,7 +8,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Collection\Actors;
use Event_Bridge_For_ActivityPub\Event_Sources;
@ -35,10 +35,10 @@ class Delete {
/**
* Handle "Follow" requests.
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
* @param array $activity The activity-object.
* @param int|int[] $user_id The id of the local blog-user.
*/
public static function handle_delete( $activity, $user_id ): void {
public static function handle_delete( array $activity, int|array $user_id ): void {
// We only process activities that are target to the application user.
if ( Actors::BLOG_USER_ID !== $user_id ) {
return;

View File

@ -1,159 +0,0 @@
<?php
/**
* Join handler file.
*
* @package Event_Bridge_For_ActivityPub
* @license AGPL-3.0-or-later
*/
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
use Activitypub\Activity\Activity;
use Activitypub\Collection\Actors;
use Activitypub\Activity\Actor;
use Activitypub\Http;
use Activitypub\Transformer\Factory;
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Event_Transformer;
use function Activitypub\get_remote_metadata_by_actor;
use function Activitypub\object_to_uri;
/**
* Handle Join requests.
*/
class Join {
/**
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_action(
'activitypub_register_handlers',
array( self::class, 'register_join_handler' )
);
\add_action(
'event_bridge_for_activitypub_ignore_join',
array( self::class, 'send_ignore_response' ),
10,
3
);
}
/**
* Register the join handler to the ActivityPub plugin.
*/
public static function register_join_handler() {
\add_action(
'activitypub_inbox_join',
array( self::class, 'handle_join' )
);
}
/**
* Handle ActivityPub "Join" requests.
*
* @param array $activity The activity object.
*/
public static function handle_join( $activity ) {
$actor = get_remote_metadata_by_actor( object_to_uri( $activity['actor'] ) );
// If we cannot fetch the actor, we cannot continue.
if ( \is_wp_error( $actor ) ) {
return;
}
// This should be already validated, but just to be sure.
if ( ! array_key_exists( 'object', $activity ) ) {
return;
}
// Get the WordPress Post ID, via the ActivityPub ID.
$post_id = self::get_post_id_by_activitypub_id( \sanitize_url( object_to_uri( $activity['object'] ) ) );
if ( ! $post_id ) {
// No post is found for this URL/ID.
return;
}
// Check whether the target object/post is an event post.
$transformer = Factory::get_transformer( get_post( $post_id ) );
if ( ! $transformer instanceof Event_Transformer ) {
return;
}
// Pass over to Event plugin specific handler if implemented here. Until then just send an ignore.
do_action(
'event_bridge_for_activitypub_ignore_join',
$transformer->get_actor_object()->get_id(), // Gets the WordPress user that "owns" the object by ActivityPub means.
$activity['id'],
Actor::init_from_array( $actor )
);
}
/**
* Send "Ignore" response.
*
* @param string $actor The actors ActivityPub ID which sends the response.
* @param string $ignored The ID of the Activity that gets ignored.
* @param Actor|mixed $to The target actor.
*/
public static function send_ignore_response( $actor, $ignored, $to ) {
// Get actor object that owns the object that was targeted by the ignored activity.
$actor = Actors::get_by_resource( $actor );
if ( \is_wp_error( $to ) || \is_wp_error( $actor ) ) {
return;
}
// Get inbox.
$inbox = $to->get_inbox();
if ( ! $inbox ) {
return;
}
// Send "Ignore" activity.
$activity = new Activity();
$activity->set_type( 'Ignore' );
$activity->set_object( \esc_url_raw( $ignored ) );
$activity->set_actor( $actor->get_id() );
$activity->set_to( $to->get_id() );
$activity->set_id( $actor->get_id() . '#ignore-' . \preg_replace( '~^https?://~', '', $ignored ) );
$activity->set_sensitive( null );
// @phpstan-ignore-next-line
Http::post( $inbox, $activity->to_json(), $actor->get__id() );
}
/**
* Get the WordPress Post ID by the ActivityPub ID.
*
* @param string $activitypub_id The ActivityPub objects ID.
* @return int The WordPress post ID.
*/
private static function get_post_id_by_activitypub_id( $activitypub_id ) {
// Parse the URL and extract its components.
$parsed_url = wp_parse_url( $activitypub_id );
$home_url = \trailingslashit( \home_url() );
// Ensure the base URL matches the home URL.
if ( \trailingslashit( "{$parsed_url['scheme']}://{$parsed_url['host']}" ) !== $home_url ) {
return 0;
}
// Check for a valid query string and parse it.
if ( isset( $parsed_url['query'] ) ) {
parse_str( $parsed_url['query'], $query_vars );
// Ensure the only parameter is 'p'.
if ( count( $query_vars ) === 1 && isset( $query_vars['p'] ) ) {
return intval( $query_vars['p'] ); // Return the post ID.
}
}
// Fallback: legacy ActivityPub plugin (before version 3.0.0) used pretty permalinks as `id`.
return \url_to_postid( $activitypub_id );
}
}

View File

@ -9,7 +9,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Collection\Actors;
use Event_Bridge_For_ActivityPub\Event_Sources;
@ -35,10 +35,10 @@ class Undo {
/**
* Handle incoming "Undo" activities.
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
* @param array $activity The activity-object.
* @param int|int[] $user_id The id of the local blog-user.
*/
public static function handle_undo( $activity, $user_id ): void {
public static function handle_undo( array $activity, int|array $user_id ): void {
// We only process activities that are target to the blog actor.
if ( Actors::BLOG_USER_ID !== $user_id ) {
return;
@ -53,6 +53,7 @@ class Undo {
global $wpdb;
// phpcs:disable WordPress.DB.DirectDatabaseQuery
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",

View File

@ -8,14 +8,7 @@
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
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;
use function Activitypub\is_activity_public;
\defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
/**
* Handle Update requests.
@ -36,41 +29,11 @@ class Update {
/**
* Handle incoming "Update" activities..
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
* @param array $activity The activity-object.
* @param int|int[] $user_id The id of the local blog-user.
*/
public static function handle_update( $activity, $user_id ): void {
// We only process activities that are target to the application user.
if ( Actors::BLOG_USER_ID !== $user_id ) {
return;
}
// Check if Activity is public or not.
if ( ! is_activity_public( $activity ) ) {
return;
}
// Check if an object is set and it is an object of type `Event`.
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
return;
}
// Check that we are actually following/or have a pending follow request this actor.
$event_source_post_id = Event_Source::get_post_id_by_activitypub_id( $activity['actor'] );
if ( ! $event_source_post_id ) {
return;
}
if ( Event_Sources::is_time_passed( $activity['object']['startTime'] ) ) {
return;
}
$transmogrifier = Setup::get_transmogrifier();
if ( ! $transmogrifier ) {
return;
}
$transmogrifier::save( $activity['object'], $event_source_post_id );
public static function handle_update( array $activity, int|array $user_id ): void {
// We handle updates the same as we handle creates for now (specification though says we should ignore it).
Create::handle_create( $activity, $user_id );
}
}