installed plugin Event Bridge for ActivityPub version 1.1.0

This commit is contained in:
2025-04-29 21:32:06 +00:00
committed by Gitium
parent fc3d7ab181
commit cf022e2628
93 changed files with 14432 additions and 0 deletions

View File

@ -0,0 +1,76 @@
<?php
/**
* Accept handler file.
*
* @package Event_Bridge_For_ActivityPub
* @since 1.0.0
* @license AGPL-3.0-or-later */
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Collection\Actors;
use Activitypub\Model\Blog;
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
use function Activitypub\object_to_uri;
/**
* Handle Accept requests.
*/
class Accept {
/**
* Initialize the class, registering the handler for incoming `Accept` activities to the ActivityPub plugin.
*/
public static function init(): void {
\add_action(
'activitypub_inbox_accept',
array( self::class, 'handle_accept' ),
15,
2
);
}
/**
* Handle incoming "Accept" activities.
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
*/
public static function handle_accept( $activity, $user_id ): void {
// We only process activities that are target to the blog actor.
if ( Actors::BLOG_USER_ID !== $user_id ) {
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;
}
// This is what the ID of the follow request would look like.
$application = new Blog();
$follow_id = Event_Sources_Collection::compose_follow_id( $application->get_id(), $activity['actor'] );
// 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;
}
// Save the accept status of the follow request to the event source post.
\update_post_meta( $event_source_post_id, '_event_bridge_for_activitypub_accept_of_follow', $activity['id'] );
\wp_update_post(
array(
'ID' => $event_source_post_id,
'post_status' => 'publish',
)
);
// Trigger the backfilling of events from this actor.
\do_action( 'event_bridge_for_activitypub_backfill_events', $event_source_post_id );
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Create handler file.
*
* @package Event_Bridge_For_ActivityPub
*/
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;
/**
* Handle Create requests.
*/
class Create {
/**
* Initialize the class, registering the handler for incoming `Create` activities to the ActivityPub plugin.
*/
public static function init(): void {
\add_action(
'activitypub_inbox_create',
array( self::class, 'handle_create' ),
15,
2
);
}
/**
* Handle incoming "Create" activities.
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
*/
public static function handle_create( $activity, $user_id ): void {
// We only process activities that are target to the blog actor.
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 );
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Delete handler file.
*
* @package Event_Bridge_For_ActivityPub
*/
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Collection\Actors;
use Event_Bridge_For_ActivityPub\Event_Sources;
use Event_Bridge_For_ActivityPub\Setup;
use function Activitypub\object_to_uri;
/**
* Handle Delete requests.
*/
class Delete {
/**
* Initialize the class, registering the handler for incoming `Delete` activities to the ActivityPub plugin.
*/
public static function init() {
\add_action(
'activitypub_inbox_delete',
array( self::class, 'handle_delete' ),
15,
2
);
}
/**
* Handle "Follow" requests.
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
*/
public static function handle_delete( $activity, $user_id ): void {
// We only process activities that are target to the application user.
if ( Actors::BLOG_USER_ID !== $user_id ) {
return;
}
// Check that we are actually following this actor.
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
return;
}
$id = object_to_uri( $activity['object'] );
$transmogrifier = Setup::get_transmogrifier();
if ( ! $transmogrifier ) {
return;
}
$transmogrifier::delete( $id );
}
}

View File

@ -0,0 +1,159 @@
<?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

@ -0,0 +1,73 @@
<?php
/**
* Undo handler file.
*
* @package Event_Bridge_For_ActivityPub
* @since 1.0.0
* @license AGPL-3.0-or-later */
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
use Activitypub\Collection\Actors;
use Event_Bridge_For_ActivityPub\Event_Sources;
use function Activitypub\object_to_uri;
/**
* Handle Uno requests.
*/
class Undo {
/**
* Initialize the class, registering the handler for incoming `Uno` activities to the ActivityPub plugin.
*/
public static function init(): void {
\add_action(
'activitypub_inbox_undo',
array( self::class, 'handle_undo' ),
15,
2
);
}
/**
* Handle incoming "Undo" activities.
*
* @param array $activity The activity-object.
* @param int $user_id The id of the local blog-user.
*/
public static function handle_undo( $activity, $user_id ): void {
// We only process activities that are target to the blog actor.
if ( Actors::BLOG_USER_ID !== $user_id ) {
return;
}
// Check that we are actually following/or have a pending follow request for this actor.
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
return;
}
$accept_id = \sanitize_url( object_to_uri( $activity['object'] ) );
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',
$accept_id
)
);
// If no event source with that accept ID is found return.
if ( empty( $results ) ) {
return;
}
$post_id = reset( $results )->post_id;
\delete_post_meta( $post_id, '_event_bridge_for_activitypub_accept_of_follow' );
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Update handler file.
*
* @package Event_Bridge_For_ActivityPub
*/
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;
/**
* Handle Update requests.
*/
class Update {
/**
* Initialize the class, registering the handler for incoming `Update` activities to the ActivityPub plugin.
*/
public static function init(): void {
\add_action(
'activitypub_inbox_update',
array( self::class, 'handle_update' ),
15,
2
);
}
/**
* Handle incoming "Update" activities..
*
* @param array $activity The activity-object.
* @param 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 );
}
}