diff --git a/includes/activitypub/collection/class-event-sources.php b/includes/activitypub/collection/class-event-sources.php index 72d3c4c..6703804 100644 --- a/includes/activitypub/collection/class-event-sources.php +++ b/includes/activitypub/collection/class-event-sources.php @@ -92,6 +92,16 @@ class Event_Sources { ) ); + \register_post_meta( + self::POST_TYPE, + 'activitypub_inbox', + array( + 'type' => 'string', + 'single' => true, + 'sanitize_callback' => 'sanitize_url', + ) + ); + \register_post_meta( self::POST_TYPE, 'event_source_utilize_announces', @@ -229,4 +239,140 @@ class Event_Sources { $post_id = Event_Source::get_wp_post_from_activitypub_actor_id( $event_source ); return wp_delete_post( $post_id, true ); } + + /** + * Queue a hook to run async. + * + * @param string $hook The hook name. + * @param array $args The arguments to pass to the hook. + * @param string $unqueue_hook Optional a hook to unschedule before queuing. + * @return void|bool Whether the hook was queued. + */ + public static function queue( $hook, $args, $unqueue_hook = null ) { + if ( $unqueue_hook ) { + $hook_timestamp = wp_next_scheduled( $unqueue_hook, $args ); + if ( $hook_timestamp ) { + wp_unschedule_event( $hook_timestamp, $unqueue_hook, $args ); + } + } + + if ( wp_next_scheduled( $hook, $args ) ) { + return; + } + + return \wp_schedule_single_event( \time(), $hook, $args ); + } + + /** + * Prepare to follow an ActivityPub actor via a scheduled event. + * + * @param string $actor The ActivityPub actor. + * + * @return bool|WP_Error Whether the event was queued. + */ + public static function queue_follow_actor( $actor ) { + $queued = self::queue( + 'event_bridge_for_activitypub_follow', + $actor, + 'event_bridge_for_activitypub_unfollow' + ); + + return $queued; + } + + /** + * Follow an ActivityPub actor via the Application user. + * + * @param string $actor_id The ID/URL of the Actor. + */ + public static function activitypub_follow_actor( $actor_id ) { + $post_id = Event_Source::get_wp_post_from_activitypub_actor_id( $actor_id ); + + if ( ! $post_id ) { + return; + } + + $actor = Event_Source::init_from_cpt( get_post( $post_id ) ); + + if ( is_wp_error( $actor ) ) { + return $actor; + } + + $inbox = $actor->get_shared_inbox(); + $to = $actor->get_id(); + + $application = new \Activitypub\Model\Application(); + + $activity = new \Activitypub\Activity\Activity(); + $activity->set_type( 'Follow' ); + $activity->set_to( null ); + $activity->set_cc( null ); + $activity->set_actor( $application->get_id() ); + $activity->set_object( $to ); + $activity->set_id( $actor . '#follow-' . \preg_replace( '~^https?://~', '', $to ) ); + $activity = $activity->to_json(); + \Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::APPLICATION_USER_ID ); + } + + /** + * Prepare to unfollow an actor via a scheduled event. + * + * @param string $actor The ActivityPub actor ID. + * + * @return bool|WP_Error Whether the event was queued. + */ + public static function queue_unfollow_actor( $actor ) { + $queued = self::queue( + 'event_bridge_for_activitypub_unfollow', + $actor, + 'event_bridge_for_activitypub_follow' + ); + + return $queued; + } + + /** + * Unfollow an ActivityPub actor. + * + * @param string $actor_id The ActivityPub actor ID. + */ + public static function activitypub_unfollow_actor( $actor_id ) { + $post_id = Event_Source::get_wp_post_from_activitypub_actor_id( $actor_id ); + + if ( ! $post_id ) { + return; + } + + $actor = Event_Source::init_from_cpt( get_post( $post_id ) ); + + if ( is_wp_error( $actor ) ) { + return $actor; + } + + $inbox = $actor->get_shared_inbox(); + $to = $actor->get_id(); + + $application = new \Activitypub\Model\Application(); + + if ( is_wp_error( $inbox ) ) { + return $inbox; + } + + $activity = new \Activitypub\Activity\Activity(); + $activity->set_type( 'Undo' ); + $activity->set_to( null ); + $activity->set_cc( null ); + $activity->set_actor( $application->get_id() ); + $activity->set_object( + array( + 'type' => 'Follow', + 'actor' => $actor, + 'object' => $to, + 'id' => $to, + ) + ); + $activity->set_id( $actor . '#unfollow-' . \preg_replace( '~^https?://~', '', $to ) ); + $activity = $activity->to_json(); + \Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::APPLICATION_USER_ID ); + } } diff --git a/includes/activitypub/model/class-event-source.php b/includes/activitypub/model/class-event-source.php index d73e2ba..b64da62 100644 --- a/includes/activitypub/model/class-event-source.php +++ b/includes/activitypub/model/class-event-source.php @@ -9,7 +9,6 @@ namespace Event_Bridge_For_ActivityPub\ActivityPub\Model; use Activitypub\Activity\Actor; -use Activitypub\Webfinger; use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources; use WP_Error; @@ -118,6 +117,32 @@ class Event_Source extends Actor { return true; } + /** + * Update the post meta. + */ + protected function get_post_meta_input() { + $meta_input = array(); + $meta_input['activitypub_inbox'] = $this->get_shared_inbox(); + $meta_input['activitypub_actor_json'] = $this->to_json(); + + return $meta_input; + } + + /** + * Get the shared inbox, with a fallback to the inbox. + * + * @return string|null The URL to the shared inbox, the inbox or null. + */ + public function get_shared_inbox() { + if ( ! empty( $this->get_endpoints()['sharedInbox'] ) ) { + return $this->get_endpoints()['sharedInbox']; + } elseif ( ! empty( $this->get_inbox() ) ) { + return $this->get_inbox(); + } + + return null; + } + /** * Save the current Event Source object to Database within custom post type. * diff --git a/includes/class-event-sources.php b/includes/class-event-sources.php index c5ea476..b636569 100644 --- a/includes/class-event-sources.php +++ b/includes/class-event-sources.php @@ -9,11 +9,8 @@ namespace Event_Bridge_For_ActivityPub; use Activitypub\Activity\Extended_Object\Event; use Activitypub\Collection\Actors; -use Activitypub\Http; -use Exception; use function Activitypub\get_remote_metadata_by_actor; -use function register_post_type; /** * Class for handling and saving the ActivityPub event sources (i.e. follows). @@ -30,83 +27,9 @@ class Event_Sources { * Constructor. */ public function __construct() { - \add_action( 'init', array( $this, 'register_post_meta' ) ); - \add_action( 'activitypub_inbox', array( $this, 'handle_activitypub_inbox' ), 15, 3 ); } - /** - * Register the post type used to store the external event sources (i.e., followed ActivityPub actors). - */ - public static function register_post_type() { - register_post_type( - self::POST_TYPE, - array( - 'labels' => array( - 'name' => _x( 'Event Sources', 'post_type plural name', 'activitypub' ), - 'singular_name' => _x( 'Event Source', 'post_type single name', 'activitypub' ), - ), - 'public' => false, - 'hierarchical' => false, - 'rewrite' => false, - 'query_var' => false, - 'delete_with_user' => false, - 'can_export' => true, - 'supports' => array(), - ) - ); - - \register_post_meta( - self::POST_TYPE, - 'activitypub_inbox', - array( - 'type' => 'string', - 'single' => true, - 'sanitize_callback' => 'sanitize_url', - ) - ); - - \register_post_meta( - self::POST_TYPE, - 'activitypub_errors', - array( - 'type' => 'string', - 'single' => false, - 'sanitize_callback' => function ( $value ) { - if ( ! is_string( $value ) ) { - throw new Exception( 'Error message is no valid string' ); - } - - return esc_sql( $value ); - }, - ) - ); - - \register_post_meta( - self::POST_TYPE, - 'activitypub_user_id', - array( - 'type' => 'string', - 'single' => false, - 'sanitize_callback' => function ( $value ) { - return esc_sql( $value ); - }, - ) - ); - - \register_post_meta( - self::POST_TYPE, - 'activitypub_actor_json', - array( - 'type' => 'string', - 'single' => true, - 'sanitize_callback' => function ( $value ) { - return sanitize_text_field( $value ); - }, - ) - ); - } - /** * Handle the ActivityPub Inbox. * diff --git a/includes/class-setup.php b/includes/class-setup.php index 841a65c..df9970a 100644 --- a/includes/class-setup.php +++ b/includes/class-setup.php @@ -20,6 +20,7 @@ use Event_Bridge_For_ActivityPub\Admin\General_Admin_Notices; use Event_Bridge_For_ActivityPub\Admin\Health_Check; use Event_Bridge_For_ActivityPub\Admin\Settings_Page; use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin; +use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection; require_once ABSPATH . 'wp-admin/includes/plugin.php'; @@ -183,7 +184,7 @@ class Setup { } add_action( 'init', array( Health_Check::class, 'init' ) ); - add_action( 'init', array( Event_Sources::class, 'register_post_type' ) ); + add_action( 'init', array( Event_Sources_Collection::class, 'register_post_type' ) ); // Check if the minimum required version of the ActivityPub plugin is installed. if ( ! version_compare( $this->activitypub_plugin_version, EVENT_BRIDGE_FOR_ACTIVITYPUB_ACTIVITYPUB_PLUGIN_MIN_VERSION ) ) { diff --git a/includes/table/class-event-sources.php b/includes/table/class-event-sources.php index 32e6794..2969483 100644 --- a/includes/table/class-event-sources.php +++ b/includes/table/class-event-sources.php @@ -95,7 +95,7 @@ class Event_Sources extends WP_List_Table { } // phpcs:enable WordPress.Security.NonceVerification.Recommended - $event_sources = Event_Sources_Collection::get_event_sources_with_count($per_page, $page_num, $args ); + $event_sources = Event_Sources_Collection::get_event_sources_with_count( $per_page, $page_num, $args ); $actors = $event_sources['actors']; $counter = $event_sources['total']; diff --git a/templates/event-sources.php b/templates/event-sources.php index c62912a..b2a4b20 100644 --- a/templates/event-sources.php +++ b/templates/event-sources.php @@ -28,7 +28,7 @@ $table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources(); - + @@ -37,8 +37,8 @@ $table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();