Add Event Sources Logic (ActivityPub follows) #86
10 changed files with 175 additions and 47 deletions
|
@ -61,7 +61,7 @@
|
|||
],
|
||||
"test-debug": [
|
||||
"@prepare-test",
|
||||
"@test-the-events-calendar"
|
||||
"@test-gatherpress"
|
||||
],
|
||||
"test-vs-event-list": "phpunit --filter=vs_event_list",
|
||||
"test-the-events-calendar": "phpunit --filter=the_events_calendar",
|
||||
|
|
|
@ -16,6 +16,7 @@ use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Accept;
|
|||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Update;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Create;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Delete;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Undo;
|
||||
|
||||
/**
|
||||
* Class responsible for registering handlers for incoming activities to the ActivityPub plugin.
|
||||
|
@ -29,5 +30,6 @@ class Handler {
|
|||
Update::init();
|
||||
Create::init();
|
||||
Delete::init();
|
||||
Undo::init();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,15 +116,6 @@ class Event_Sources {
|
|||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'event_source_active',
|
||||
array(
|
||||
'type' => 'bool',
|
||||
'single' => true,
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'activitypub_inbox',
|
||||
|
@ -137,7 +128,7 @@ class Event_Sources {
|
|||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'event_source_utilize_announces',
|
||||
'_event_bridge_for_activitypub_utilize_announces',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
|
@ -149,6 +140,16 @@ class Event_Sources {
|
|||
},
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_event_bridge_for_activitypub_accept_of_follow',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,13 +179,24 @@ class Event_Sources {
|
|||
return $post_id;
|
||||
}
|
||||
|
||||
$success = self::queue_follow_actor( $actor );
|
||||
self::queue_follow_actor( $actor );
|
||||
|
||||
self::delete_event_source_transients();
|
||||
|
||||
return $event_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the ActivityPub ID of a follow request.
|
||||
*
|
||||
* @param string $follower_id The ActivityPub ID of the actor that followers the other one.
|
||||
* @param string $followed_id The ActivityPub ID of the followed actor.
|
||||
* @return string The `Follow` ID.
|
||||
*/
|
||||
public static function compose_follow_id( $follower_id, $followed_id ) {
|
||||
return $follower_id . '#follow-' . \preg_replace( '~^https?://~', '', $followed_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all transients related to the event sources.
|
||||
*
|
||||
|
@ -226,6 +238,7 @@ class Event_Sources {
|
|||
|
||||
// If the deletion was successful delete all transients regarding event sources.
|
||||
if ( $result ) {
|
||||
self::queue_unfollow_actor( $actor );
|
||||
self::delete_event_source_transients();
|
||||
}
|
||||
|
||||
|
@ -350,7 +363,7 @@ class Event_Sources {
|
|||
$activity->set_cc( null );
|
||||
$activity->set_actor( $application->get_id() );
|
||||
$activity->set_object( $to );
|
||||
$activity->set_id( $application->get_id() . '#follow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity->set_id( self::compose_follow_id( $application->get_id(), $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::BLOG_USER_ID );
|
||||
}
|
||||
|
@ -375,13 +388,11 @@ class Event_Sources {
|
|||
/**
|
||||
* Unfollow an ActivityPub actor.
|
||||
*
|
||||
* @param string $actor_id The ActivityPub actor ID.
|
||||
* @param Event_Source $actor The ActivityPub actor model.
|
||||
*/
|
||||
public static function activitypub_unfollow_actor( $actor_id ) {
|
||||
$actor = Event_Source::get_by_id( $actor_id );
|
||||
|
||||
if ( ! $actor ) {
|
||||
return $actor;
|
||||
public static function activitypub_unfollow_actor( $actor ) {
|
||||
if ( ! $actor instanceof Event_Source ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$inbox = $actor->get_shared_inbox();
|
||||
|
@ -406,7 +417,7 @@ class Event_Sources {
|
|||
'id' => $to,
|
||||
)
|
||||
);
|
||||
$activity->set_id( $actor . '#unfollow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity->set_id( $application->get_id() . '#unfollow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::BLOG_USER_ID );
|
||||
}
|
||||
|
|
|
@ -9,6 +9,12 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
* Handle Accept requests.
|
||||
|
@ -20,30 +26,39 @@ class Accept {
|
|||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_accept',
|
||||
array( self::class, 'handle_accept' )
|
||||
array( self::class, 'handle_accept' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Accept" activities.
|
||||
*
|
||||
* @param array $activity The activity object.
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_accept( $activity ) {
|
||||
if ( ! isset( $activity['object'] ) ) {
|
||||
public static function handle_accept( $activity, $user_id ) {
|
||||
// We only process activities that are target to the blog actor.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$object = Actors::get_by_resource( $activity['object'] );
|
||||
|
||||
if ( ! $object || is_wp_error( $object ) ) {
|
||||
// If we can not find a actor, we handle the `Accept` activity.
|
||||
// Check that we are actually following/or have a pending follow request this actor.
|
||||
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only expect `Accept` activities being answers to follow requests by the application actor.
|
||||
if ( Actors::BLOG_USER_ID !== $object->get__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'] );
|
||||
|
||||
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'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ class Create {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set.
|
||||
// Check if an object is set and it is an object of type `Event`.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
|
77
includes/activitypub/handler/class-undo.php
Normal file
77
includes/activitypub/handler/class-undo.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?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;
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Handle Uno requests.
|
||||
*/
|
||||
class Undo {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Uno` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init() {
|
||||
\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 ) {
|
||||
// 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.
|
||||
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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' => '=',
|
||||
),
|
||||
),
|
||||
);
|
||||
$query = new \WP_Query( $args );
|
||||
|
||||
// If no event source with that accept ID is found return.
|
||||
if ( ! $query->have_posts ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post = $query->get_posts()[0];
|
||||
|
||||
$post_id = is_a( $post, 'WP_Post' ) ? $post->ID : $post;
|
||||
|
||||
\delete_post_meta( $post_id, '_event_bridge_for_activitypub_accept_of_follow' );
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ class Update {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set.
|
||||
// Check if an object is set and it is an object of type `Event`.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -94,10 +94,10 @@ class Event_Source extends Actor {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the WordPress post which stores the Event Source by the ActivityPub actor id of the event source.
|
||||
* Get the Event Source by the ActivityPub ID.
|
||||
*
|
||||
* @param string $actor_id The ActivityPub actor ID.
|
||||
* @return ?Event_Source The WordPress post ID if the actor is found, null if not.
|
||||
* @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 );
|
||||
|
@ -105,7 +105,7 @@ class Event_Source extends Actor {
|
|||
if ( $post ) {
|
||||
$actor = self::init_from_cpt( $post );
|
||||
}
|
||||
return $actor ?? null;
|
||||
return $actor ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,13 +42,19 @@ class Event_Sources {
|
|||
// Register handlers for incoming activities to the ActivityPub plugin, e.g. incoming `Event` objects.
|
||||
\add_action( 'activitypub_register_handlers', array( Handler::class, 'register_handlers' ) );
|
||||
|
||||
// Add validation filter, so that only plausible event objects reach the handlers above.
|
||||
// Add validation filter, so that only plausible activities reach the handlers above.
|
||||
\add_filter(
|
||||
'activitypub_validate_object',
|
||||
array( self::class, 'validate_event_object' ),
|
||||
12,
|
||||
3
|
||||
);
|
||||
\add_filter(
|
||||
'activitypub_validate_object',
|
||||
array( self::class, 'validate_activity' ),
|
||||
13,
|
||||
3
|
||||
);
|
||||
|
||||
// Apply modifications to the UI, e.g. disable editing of remote event posts.
|
||||
\add_action( 'init', array( User_Interface::class, 'init' ) );
|
||||
|
@ -335,6 +341,27 @@ class Event_Sources {
|
|||
return array_merge( $hosts, $event_sources_hosts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the event object.
|
||||
*
|
||||
* @param bool $valid The validation state.
|
||||
* @param string $param The object parameter.
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return bool|WP_Error The validation state: true if valid, false if not.
|
||||
*/
|
||||
public static function validate_activity( $valid, $param, $request ) {
|
||||
if ( $valid ) {
|
||||
return $valid;
|
||||
}
|
||||
$json_params = $request->get_json_params();
|
||||
|
||||
if ( isset( $json_params['object']['type'] ) && in_array( $json_params['object']['type'], array( 'Accept', 'Undo' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the event object.
|
||||
|
|
|
@ -48,7 +48,7 @@ class Event_Sources extends WP_List_Table {
|
|||
'cb' => '<input type="checkbox" />',
|
||||
'icon' => \__( 'Icon', 'event-bridge-for-activitypub' ),
|
||||
'name' => \__( 'Name', 'event-bridge-for-activitypub' ),
|
||||
'active' => \__( 'Active', 'event-bridge-for-activitypub' ),
|
||||
'accepted' => \__( 'Follow', 'event-bridge-for-activitypub' ),
|
||||
'url' => \__( 'URL', 'event-bridge-for-activitypub' ),
|
||||
'published' => \__( 'Followed', 'event-bridge-for-activitypub' ),
|
||||
'modified' => \__( 'Last updated', 'event-bridge-for-activitypub' ),
|
||||
|
@ -118,7 +118,7 @@ class Event_Sources extends WP_List_Table {
|
|||
'icon' => esc_attr( $actor->get_icon_url() ),
|
||||
'name' => esc_attr( $actor->get_name() ),
|
||||
'url' => esc_attr( object_to_uri( $actor->get_id() ) ),
|
||||
'active' => esc_attr( get_post_meta( $actor->get__id(), 'event_source_active', true ) ),
|
||||
'accepted' => esc_attr( get_post_meta( $actor->get__id(), '_event_bridge_for_activitypub_accept_of_follow', true ) ),
|
||||
'identifier' => esc_attr( $actor->get_id() ),
|
||||
'published' => esc_attr( $actor->get_published() ),
|
||||
'modified' => esc_attr( $actor->get_updated() ),
|
||||
|
@ -196,16 +196,12 @@ class Event_Sources extends WP_List_Table {
|
|||
* @param array $item Item.
|
||||
* @return string
|
||||
*/
|
||||
public function column_active( $item ) {
|
||||
if ( $item['active'] ) {
|
||||
$action = 'true';
|
||||
public function column_accepted( $item ) {
|
||||
if ( $item['accepted'] ) {
|
||||
return esc_html__( 'Accepted', 'event-bridge-for-activitypub' );
|
||||
} else {
|
||||
$action = 'false';
|
||||
return esc_html__( 'Pending', 'event-bridge-for-activitypub' );
|
||||
}
|
||||
return sprintf(
|
||||
'%s',
|
||||
$action
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue