decouple the adding/removing of follow relationships from adding/removing followers
Some checks are pending
PHP_CodeSniffer / phpcs (push) Waiting to run
Unit Testing / phpunit (5.6, 6.2) (push) Waiting to run
Unit Testing / phpunit (7.0) (push) Waiting to run
Unit Testing / phpunit (7.2) (push) Waiting to run
Unit Testing / phpunit (7.3) (push) Waiting to run
Unit Testing / phpunit (7.4) (push) Waiting to run
Unit Testing / phpunit (8.0) (push) Waiting to run
Unit Testing / phpunit (8.1) (push) Waiting to run
Unit Testing / phpunit (8.2) (push) Waiting to run
Unit Testing / phpunit (latest) (push) Waiting to run

also use post_content, and post_content_filtered to store the followers json object and inbox

fix phpcs

remove commented out code
This commit is contained in:
André Menrath 2023-12-27 15:39:53 +01:00
parent 32e08d7f68
commit 32acc511b1
8 changed files with 121 additions and 123 deletions

View file

@ -572,7 +572,7 @@ class Base_Object {
/** /**
* Convert JSON input to an array. * 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. * @return \Activitypub\Activity\Base_Object An Object built from the JSON string.
*/ */

View file

@ -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' ); do_action( 'activitypub_after_register_post_type' );
} }

View file

@ -21,7 +21,9 @@ class Followers {
const CACHE_KEY_INBOXES = 'follower_inboxes_%s'; 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 int $user_id The ID of the WordPress User
* @param string $actor The Actor URL * @param string $actor The Actor URL
@ -49,6 +51,16 @@ class Followers {
return $follower_id; 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' ); $post_meta = get_post_meta( $follower_id, 'activitypub_user_id' );
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
@ -59,8 +71,6 @@ class Followers {
// Reset the cached inboxes for the followed user // Reset the cached inboxes for the followed user
wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); 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. * @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' ); wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
$follower = self::get_follower( $user_id, $actor ); $follower = self::get_follower( $user_id, $actor );
@ -114,6 +124,18 @@ class Followers {
return null; 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. * 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 ); $args = wp_parse_args( $args, $defaults );
$query = new WP_Query( $args ); $query = new WP_Query( $args );
$total = $query->found_posts; $total = $query->found_posts;
@ -204,18 +226,6 @@ class Followers {
public static function get_all_followers() { public static function get_all_followers() {
$args = array( $args = array(
'nopaging' => true, '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 ); return self::get_followers( null, null, null, $args );
} }
@ -239,18 +249,10 @@ class Followers {
'key' => 'activitypub_user_id', 'key' => 'activitypub_user_id',
'value' => $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; return $query->found_posts;
} }
@ -263,6 +265,7 @@ class Followers {
*/ */
public static function get_inboxes( $user_id ) { public static function get_inboxes( $user_id ) {
$cache_key = sprintf( self::CACHE_KEY_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' ); // $inboxes = wp_cache_get( $cache_key, 'activitypub' );
// if ( $inboxes ) { // if ( $inboxes ) {
@ -278,19 +281,10 @@ class Followers {
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array( 'meta_query' => array(
'relation' => 'AND', 'relation' => 'AND',
array(
'key' => 'activitypub_inbox',
'compare' => 'EXISTS',
),
array( array(
'key' => 'activitypub_user_id', 'key' => 'activitypub_user_id',
'value' => $user_id, 'value' => $user_id,
), ),
array(
'key' => 'activitypub_inbox',
'value' => '',
'compare' => '!=',
),
), ),
) )
); );
@ -301,42 +295,16 @@ class Followers {
return array(); 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; global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$results = $wpdb->get_col( $inboxes = $wpdb->get_col(
$wpdb->prepare( $wpdb->prepare(
"SELECT DISTINCT meta_value FROM {$wpdb->postmeta} "SELECT DISTINCT post_content_filtered FROM {$wpdb->posts}
WHERE post_id IN (" . implode( ', ', array_fill( 0, count( $follower_ids ), '%d' ) ) . ") WHERE ID IN (" . implode( ', ', array_fill( 0, count( $follower_ids ), '%d' ) ) . ')',
AND meta_key = 'activitypub_inbox'
AND meta_value IS NOT NULL",
$follower_ids $follower_ids
) )
); );
$inboxes = array_filter( $results );
wp_cache_set( $cache_key, $inboxes, 'activitypub' ); wp_cache_set( $cache_key, $inboxes, 'activitypub' );
return $inboxes; return $inboxes;

View file

@ -75,17 +75,17 @@ class Follow {
* *
* @return void * @return void
*/ */
public static function send_follow_response( $user, $follower, $object, $type ) { public static function send_follow_response( $user, $inbox, $object, $type ) {
// send activity // send activity
$activity = new Activity(); $activity = new Activity();
$activity->set_type( $type ); $activity->set_type( $type );
$activity->set_object( $object ); $activity->set_object( $object );
$activity->set_actor( $user->get_id() ); $activity->set_actor( $user->get_id() );
$activity->set_to( $follower->get_id() ); $activity->set_to( $object['actor'] );
$activity->set_id( $user->get_id() . '#accept-' . \preg_replace( '~^https?://~', '', $follower->get_id() ) . '-' . \time() ); $activity->set_id( $user->get_id() . '#accept-' . \preg_replace( '~^https?://~', '', $object['actor'] ) . '-' . \time() );
$activity = $activity->to_json(); $activity = $activity->to_json();
Http::post( $follower->get_shared_inbox(), $activity, $user->get__id() ); Http::post( $inbox, $activity, $user->get__id() );
} }
} }

View file

@ -23,6 +23,8 @@ class Undo {
* *
* @param array $activity The JSON "Undo" Activity * @param array $activity The JSON "Undo" Activity
* @param int $user_id The ID of the ID of the WordPress User * @param int $user_id The ID of the ID of the WordPress User
*
* @return void
*/ */
public static function handle_undo( $activity ) { public static function handle_undo( $activity ) {
if ( if (

View file

@ -3,11 +3,11 @@ namespace Activitypub\Model;
use WP_Error; use WP_Error;
use Activitypub\Activity\Activity;
use Activitypub\Activity\Base_Object; use Activitypub\Activity\Base_Object;
use Activitypub\Collection\Followers;
use Activitypub\Collection\Users; use Activitypub\Collection\Users;
use Activitypub\Model\Follower; use Activitypub\Model\Follower;
use Activitypub\Http;
/** /**
* ActivityPub Follow Class * ActivityPub Follow Class
@ -21,6 +21,8 @@ use Activitypub\Http;
*/ */
class Follow_Request extends Base_Object { class Follow_Request extends Base_Object {
const FOLLOW_REQUEST_POST_TYPE = 'ap_follow_request'; 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 * 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; 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. * 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( wp_update_post(
array( array(
'ID' => $this->get__id(), '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->send_response( 'Reject' );
$this->delete(); $this->delete();
} }
/** /**
* Approve the follow request * Approve the follow request.
*/ */
public function approve() { public function approve() {
wp_update_post( $user_id = get_post_meta( $this->get__id(), 'activitypub_user_id', true );
array( $follower_id = $this->get_follower_id();
'ID' => $this->get__id(),
'post_status' => 'approved', Followers::add_follow_relationship( $user_id, $follower_id );
)
); $this->save_follow_request_status( self::FOLLOW_REQUEST_STATUS_APPROVED );
$this->send_response( 'Accept' ); $this->send_response( 'Accept' );
} }
/** /**
* Delete the follow request * Delete the follow request.
* *
* This should only be called after it has been rejected. * 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 ) { public function send_response( $type ) {
$user_id = get_post_meta( $this->get__id(), 'activitypub_user_id', true ); $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_id = $this->get_follower_id();
$follower = Follower::init_from_cpt( get_post( $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(), 'id' => $this->get_id(),
'type' => $this->get_type(), 'type' => $this->get_type(),
'actor' => $actor,
'object' => $user, 'object' => $user,
); );
do_action( 'activitypub_send_follow_response', $user, $follower, $object, $type ); do_action( 'activitypub_send_follow_response', $user, $follower_inbox, $follow_object, $type );
} }
} }

View file

@ -170,15 +170,17 @@ class Follower extends Actor {
} }
$args = array( $args = array(
'ID' => $this->get__id(), 'ID' => $this->get__id(),
'guid' => esc_url_raw( $this->get_id() ), 'guid' => esc_url_raw( $this->get_id() ),
'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ), 'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ),
'post_author' => 0, 'post_author' => 0,
'post_type' => Followers::POST_TYPE, 'post_type' => Followers::POST_TYPE,
'post_name' => esc_url_raw( $this->get_id() ), 'post_name' => esc_url_raw( $this->get_id() ),
'post_excerpt' => sanitize_text_field( wp_kses( $this->get_summary(), 'user_description' ) ), 'post_excerpt' => sanitize_text_field( wp_kses( $this->get_summary(), 'user_description' ) ),
'post_status' => 'publish', 'post_status' => 'publish',
'meta_input' => $this->get_post_meta_input(), 'post_content' => $this->to_json(),
'post_mime_type' => 'application/json',
'post_content_filtered' => $this->get_shared_inbox(),
); );
$post_id = wp_insert_post( $args ); $post_id = wp_insert_post( $args );
@ -202,7 +204,7 @@ class Follower extends Actor {
* Beware that this os deleting a Follower for ALL users!!! * Beware that this os deleting a Follower for ALL users!!!
* *
* To delete only the User connection (unfollow) * To delete only the User connection (unfollow)
* @see \Activitypub\Rest\Followers::remove_follower() * @see \Activitypub\Rest\Followers::remove_follow_relationship()
* *
* @return void * @return void
*/ */
@ -210,18 +212,20 @@ class Follower extends Actor {
wp_delete_post( $this->_id ); wp_delete_post( $this->_id );
} }
/** // /**
* Update the post meta. // * Update the post meta.
* // *
* @return void // * @return void
*/ // */
protected function get_post_meta_input() { // protected function get_post_meta_input() {
$meta_input = array(); // $meta_input = array();
$meta_input['activitypub_inbox'] = $this->get_shared_inbox(); // // TODO: migrations - moved to post_content
$meta_input['activitypub_actor_json'] = $this->to_json(); // $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. * Get the icon.

View file

@ -185,7 +185,7 @@ class Test_Activitypub_Followers extends WP_UnitTestCase {
$follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' ); $follower2 = \Activitypub\Collection\Followers::get_follower( 2, 'https://example.com/author/jon' );
$this->assertEquals( 'https://example.com/author/jon', $follower2->get_url() ); $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' ); $follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
$this->assertNull( $follower ); $this->assertNull( $follower );