diff --git a/includes/activity/class-base-object.php b/includes/activity/class-base-object.php index 218e785..8dbe406 100644 --- a/includes/activity/class-base-object.php +++ b/includes/activity/class-base-object.php @@ -572,7 +572,7 @@ class Base_Object { /** * Convert JSON input to an array. * - * @return string The object array. + * @param string The object array. * * @return \Activitypub\Activity\Base_Object An Object built from the JSON string. */ diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index 8b2a405..051aed4 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -444,18 +444,6 @@ class Activitypub { ) ); - register_post_meta( - Followers::POST_TYPE, - 'activitypub_actor_json', - array( - 'type' => 'string', - 'single' => true, - 'sanitize_callback' => function ( $value ) { - return sanitize_text_field( $value ); - }, - ) - ); - do_action( 'activitypub_after_register_post_type' ); } diff --git a/includes/collection/class-followers.php b/includes/collection/class-followers.php index 8d83805..05b653d 100644 --- a/includes/collection/class-followers.php +++ b/includes/collection/class-followers.php @@ -21,7 +21,9 @@ class Followers { const CACHE_KEY_INBOXES = 'follower_inboxes_%s'; /** - * Add new Follower + * Add new Follower. + * + * This does not add the follow relationship. It is added when the Accept respone is sent. * * @param int $user_id The ID of the WordPress User * @param string $actor The Actor URL @@ -49,6 +51,16 @@ class Followers { return $follower_id; } + return $follower; + } + + /** + * Add the follow relationship. + * + * @param int|string $user_id The internal id of the target WordPress user that gets followed. + * @param int|string $follower_id The internal id of the follower actor. + */ + public static function add_follow_relationship( $user_id, $follower_id ) { $post_meta = get_post_meta( $follower_id, 'activitypub_user_id' ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict @@ -59,8 +71,6 @@ class Followers { // Reset the cached inboxes for the followed user wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); } - - return $follower; } /** @@ -71,7 +81,7 @@ class Followers { * * @return bool|WP_Error True on success, false or WP_Error on failure. */ - public static function remove_follower( $user_id, $actor ) { + public static function remove_follow_relationship( $user_id, $actor ) { wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); $follower = self::get_follower( $user_id, $actor ); @@ -114,6 +124,18 @@ class Followers { return null; } + /** + * Get a Follower by the internal ID. + * + * @param int $user_id The post ID of the WordPress Follower custom post type post. + * + * @return \Activitypub\Model\Follower|null The Follower object or null + */ + public static function get_follower_by_id( $follower_id ) { + $post = get_post( $follower_id ); + return Follower::init_from_cpt( $post ); + } + /** * Get a Follower by Actor indepenent from the User. * @@ -181,7 +203,7 @@ class Followers { ), ), ); - + // TODO: handle follower with empty post_content or inbox which is saved in post_content_filtered $args = wp_parse_args( $args, $defaults ); $query = new WP_Query( $args ); $total = $query->found_posts; @@ -204,18 +226,6 @@ class Followers { public static function get_all_followers() { $args = array( 'nopaging' => true, - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query - 'meta_query' => array( - 'relation' => 'AND', - array( - 'key' => 'activitypub_inbox', - 'compare' => 'EXISTS', - ), - array( - 'key' => 'activitypub_actor_json', - 'compare' => 'EXISTS', - ), - ), ); return self::get_followers( null, null, null, $args ); } @@ -239,18 +249,10 @@ class Followers { 'key' => 'activitypub_user_id', 'value' => $user_id, ), - array( - 'key' => 'activitypub_inbox', - 'compare' => 'EXISTS', - ), - array( - 'key' => 'activitypub_actor_json', - 'compare' => 'EXISTS', - ), ), ) ); - + // TODO: handle follower with empty post_content or inbox which is saved in post_content_filtered return $query->found_posts; } @@ -263,6 +265,7 @@ class Followers { */ public static function get_inboxes( $user_id ) { $cache_key = sprintf( self::CACHE_KEY_INBOXES, $user_id ); + // TODO: enable caching of the inboxes: this is only for debugging purpose. // $inboxes = wp_cache_get( $cache_key, 'activitypub' ); // if ( $inboxes ) { @@ -278,19 +281,10 @@ class Followers { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( 'relation' => 'AND', - array( - 'key' => 'activitypub_inbox', - 'compare' => 'EXISTS', - ), array( 'key' => 'activitypub_user_id', 'value' => $user_id, ), - array( - 'key' => 'activitypub_inbox', - 'value' => '', - 'compare' => '!=', - ), ), ) ); @@ -301,42 +295,16 @@ class Followers { return array(); } - $user = Users::get_by_id( $user_id ); - - if ( $user->get_manually_approved_followers() ) { - $accepted_follow_requests_query = new WP_Query( - array( - 'nopaging' => true, - 'post_type' => 'ap_follow_request', - 'fields' => 'id=>parent', - 'post_status' => 'publish', - 'post_parent__in' => $follower_ids, - // phpcs:ign ore WordPress.DB.SlowDBQuery.slow_db_query_meta_query - 'meta_query' => array( - 'relation' => 'AND', - array( - 'key' => 'activitypub_user_id', - 'value' => $user_id, - ), - ), - ) - ); - } - $accepted_follow_requests = $accepted_follow_requests_query->get_posts(); - global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery - $results = $wpdb->get_col( + $inboxes = $wpdb->get_col( $wpdb->prepare( - "SELECT DISTINCT meta_value FROM {$wpdb->postmeta} - WHERE post_id IN (" . implode( ', ', array_fill( 0, count( $follower_ids ), '%d' ) ) . ") - AND meta_key = 'activitypub_inbox' - AND meta_value IS NOT NULL", + "SELECT DISTINCT post_content_filtered FROM {$wpdb->posts} + WHERE ID IN (" . implode( ', ', array_fill( 0, count( $follower_ids ), '%d' ) ) . ')', $follower_ids ) ); - $inboxes = array_filter( $results ); wp_cache_set( $cache_key, $inboxes, 'activitypub' ); return $inboxes; diff --git a/includes/handler/class-follow.php b/includes/handler/class-follow.php index e935cfd..4d70576 100644 --- a/includes/handler/class-follow.php +++ b/includes/handler/class-follow.php @@ -75,17 +75,17 @@ class Follow { * * @return void */ - public static function send_follow_response( $user, $follower, $object, $type ) { + public static function send_follow_response( $user, $inbox, $object, $type ) { // send activity $activity = new Activity(); $activity->set_type( $type ); $activity->set_object( $object ); $activity->set_actor( $user->get_id() ); - $activity->set_to( $follower->get_id() ); - $activity->set_id( $user->get_id() . '#accept-' . \preg_replace( '~^https?://~', '', $follower->get_id() ) . '-' . \time() ); + $activity->set_to( $object['actor'] ); + $activity->set_id( $user->get_id() . '#accept-' . \preg_replace( '~^https?://~', '', $object['actor'] ) . '-' . \time() ); $activity = $activity->to_json(); - Http::post( $follower->get_shared_inbox(), $activity, $user->get__id() ); + Http::post( $inbox, $activity, $user->get__id() ); } } diff --git a/includes/handler/class-undo.php b/includes/handler/class-undo.php index 70d6c4e..b686958 100644 --- a/includes/handler/class-undo.php +++ b/includes/handler/class-undo.php @@ -23,6 +23,8 @@ class Undo { * * @param array $activity The JSON "Undo" Activity * @param int $user_id The ID of the ID of the WordPress User + * + * @return void */ public static function handle_undo( $activity ) { if ( diff --git a/includes/model/class-follow-request.php b/includes/model/class-follow-request.php index 1b380d2..1b4f07d 100644 --- a/includes/model/class-follow-request.php +++ b/includes/model/class-follow-request.php @@ -3,11 +3,11 @@ namespace Activitypub\Model; use WP_Error; -use Activitypub\Activity\Activity; use Activitypub\Activity\Base_Object; +use Activitypub\Collection\Followers; use Activitypub\Collection\Users; use Activitypub\Model\Follower; -use Activitypub\Http; + /** * ActivityPub Follow Class @@ -21,6 +21,8 @@ use Activitypub\Http; */ class Follow_Request extends Base_Object { const FOLLOW_REQUEST_POST_TYPE = 'ap_follow_request'; + const FOLLOW_REQUEST_STATUS_APPROVED = 'approved'; + const FOLLOW_REQUEST_STATUS_REJECTED = 'rejected'; /** * Stores theinternal WordPress post id of the post of type ap_follow_request @@ -113,6 +115,13 @@ class Follow_Request extends Base_Object { return $follow_request; } + /** + * Get the internal ID of the follower who issues the follow request. + */ + private function get_follower_id() { + return wp_get_post_parent_id( $this->get__id() ); + } + /** * Save the current Follower-Object. * @@ -155,34 +164,62 @@ class Follow_Request extends Base_Object { } /** - * Reject the follow request + * Get the URL/URI of the Follower that issued the follow request. + * + * @return string The url/uri of the follower. */ - public function reject() { + private function get_follower_actor() { + $follower_id = wp_get_post_parent_id( $this->get__id() ); + $follower = Follower::init_from_cpt( get_post( $follower_id ) ); + return $follower->get_id(); + } + + /** + * Save the status of the follow request. + * + * @param string $status The new status of the follow request. + */ + private function save_follow_request_status( $status ) { wp_update_post( array( 'ID' => $this->get__id(), - 'post_status' => 'rejected', + 'post_status' => $status, ) ); + } + + /** + * Reject the follow request. + */ + public function reject() { + $user_id = get_post_meta( $this->get__id(), 'activitypub_user_id', true ); + $actor = $this->get_follower_actor(); + + Followers::remove_follow_relationship( $user_id, $actor ); + + $this->save_follow_request_status( self::FOLLOW_REQUEST_STATUS_REJECTED ); + $this->send_response( 'Reject' ); + $this->delete(); } /** - * Approve the follow request + * Approve the follow request. */ public function approve() { - wp_update_post( - array( - 'ID' => $this->get__id(), - 'post_status' => 'approved', - ) - ); + $user_id = get_post_meta( $this->get__id(), 'activitypub_user_id', true ); + $follower_id = $this->get_follower_id(); + + Followers::add_follow_relationship( $user_id, $follower_id ); + + $this->save_follow_request_status( self::FOLLOW_REQUEST_STATUS_APPROVED ); + $this->send_response( 'Accept' ); } /** - * Delete the follow request + * Delete the follow request. * * This should only be called after it has been rejected. */ @@ -195,20 +232,19 @@ class Follow_Request extends Base_Object { */ public function send_response( $type ) { $user_id = get_post_meta( $this->get__id(), 'activitypub_user_id', true ); - $user = Users::get_by_id( $user_id ); + $user = Users::get_by_id( $user_id ); - $follower_id = wp_get_post_parent_id( $this->get__id() ); - $follower = Follower::init_from_cpt( get_post( $follower_id ) ); + $follower_id = $this->get_follower_id(); - $actor = $follower->get_id(); + $follower_inbox = get_post_field( 'post_content_filtered', $follower_id ); - $object = array( + // Reconstruct the follow_object. + $follow_object = array( 'id' => $this->get_id(), 'type' => $this->get_type(), - 'actor' => $actor, 'object' => $user, ); - do_action( 'activitypub_send_follow_response', $user, $follower, $object, $type ); + do_action( 'activitypub_send_follow_response', $user, $follower_inbox, $follow_object, $type ); } } diff --git a/includes/model/class-follower.php b/includes/model/class-follower.php index 442db0a..1a37645 100644 --- a/includes/model/class-follower.php +++ b/includes/model/class-follower.php @@ -170,15 +170,17 @@ class Follower extends Actor { } $args = array( - 'ID' => $this->get__id(), - 'guid' => esc_url_raw( $this->get_id() ), - 'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ), - 'post_author' => 0, - 'post_type' => Followers::POST_TYPE, - 'post_name' => esc_url_raw( $this->get_id() ), - 'post_excerpt' => sanitize_text_field( wp_kses( $this->get_summary(), 'user_description' ) ), - 'post_status' => 'publish', - 'meta_input' => $this->get_post_meta_input(), + 'ID' => $this->get__id(), + 'guid' => esc_url_raw( $this->get_id() ), + 'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ), + 'post_author' => 0, + 'post_type' => Followers::POST_TYPE, + 'post_name' => esc_url_raw( $this->get_id() ), + 'post_excerpt' => sanitize_text_field( wp_kses( $this->get_summary(), 'user_description' ) ), + 'post_status' => 'publish', + 'post_content' => $this->to_json(), + 'post_mime_type' => 'application/json', + 'post_content_filtered' => $this->get_shared_inbox(), ); $post_id = wp_insert_post( $args ); @@ -202,7 +204,7 @@ class Follower extends Actor { * Beware that this os deleting a Follower for ALL users!!! * * To delete only the User connection (unfollow) - * @see \Activitypub\Rest\Followers::remove_follower() + * @see \Activitypub\Rest\Followers::remove_follow_relationship() * * @return void */ @@ -210,18 +212,20 @@ class Follower extends Actor { wp_delete_post( $this->_id ); } - /** - * Update the post meta. - * - * @return void - */ - 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(); + // /** + // * Update the post meta. + // * + // * @return void + // */ + // protected function get_post_meta_input() { + // $meta_input = array(); + // // TODO: migrations - moved to post_content + // $meta_input['activitypub_inbox'] = $this->get_shared_inbox(); + // // TODO: migrations - moved to post_content_filtered + // $meta_input['activitypub_actor_json'] = $this->to_json(); - return $meta_input; - } + // return $meta_input; + // } /** * Get the icon. diff --git a/tests/test-class-activitypub-followers.php b/tests/test-class-activitypub-followers.php index 8d5fb32..90323fa 100644 --- a/tests/test-class-activitypub-followers.php +++ b/tests/test-class-activitypub-followers.php @@ -185,7 +185,7 @@ class Test_Activitypub_Followers extends WP_UnitTestCase { $follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' ); $this->assertEquals( 'https://example.com/author/jon', $follower2->get_url() ); - \Activitypub\Collection\Followers::remove_follower( 1, 'https://example.com/author/jon' ); + \Activitypub\Collection\Followers::remove_follow_relationship( 1, 'https://example.com/author/jon' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' ); $this->assertNull( $follower );