diff --git a/composer.json b/composer.json index 09cd0ed..06122c9 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/includes/activitypub/class-handler.php b/includes/activitypub/class-handler.php index 0fa1433..3081c01 100644 --- a/includes/activitypub/class-handler.php +++ b/includes/activitypub/class-handler.php @@ -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(); } } diff --git a/includes/activitypub/collection/class-event-sources.php b/includes/activitypub/collection/class-event-sources.php index 208fb81..51d979f 100644 --- a/includes/activitypub/collection/class-event-sources.php +++ b/includes/activitypub/collection/class-event-sources.php @@ -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 ); } diff --git a/includes/activitypub/handler/class-accept.php b/includes/activitypub/handler/class-accept.php index 9e3aa2e..7cdf1e6 100644 --- a/includes/activitypub/handler/class-accept.php +++ b/includes/activitypub/handler/class-accept.php @@ -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'] ); } } } diff --git a/includes/activitypub/handler/class-create.php b/includes/activitypub/handler/class-create.php index eb1a018..10e0864 100644 --- a/includes/activitypub/handler/class-create.php +++ b/includes/activitypub/handler/class-create.php @@ -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; } diff --git a/includes/activitypub/handler/class-undo.php b/includes/activitypub/handler/class-undo.php new file mode 100644 index 0000000..f95ef99 --- /dev/null +++ b/includes/activitypub/handler/class-undo.php @@ -0,0 +1,77 @@ + 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' ); + } +} diff --git a/includes/activitypub/handler/class-update.php b/includes/activitypub/handler/class-update.php index 0ebc6c3..e106641 100644 --- a/includes/activitypub/handler/class-update.php +++ b/includes/activitypub/handler/class-update.php @@ -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; } diff --git a/includes/activitypub/model/class-event-source.php b/includes/activitypub/model/class-event-source.php index 0838980..494024d 100644 --- a/includes/activitypub/model/class-event-source.php +++ b/includes/activitypub/model/class-event-source.php @@ -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; } /** diff --git a/includes/class-event-sources.php b/includes/class-event-sources.php index 4d16531..960dc4c 100644 --- a/includes/class-event-sources.php +++ b/includes/class-event-sources.php @@ -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. diff --git a/includes/table/class-event-sources.php b/includes/table/class-event-sources.php index 533c25e..78c74de 100644 --- a/includes/table/class-event-sources.php +++ b/includes/table/class-event-sources.php @@ -48,7 +48,7 @@ class Event_Sources extends WP_List_Table { 'cb' => '', '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 - ); } /**