From 3cda64a255f87a4a07de3e9c47ff2b3d7c091222 Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Fri, 22 Dec 2023 04:33:25 -0600 Subject: [PATCH] Profiles: `Update` followers when profile fields change (#542) * Profiles: update followers when profile fields change * use static * only try to merge mention inboxes when valid * cleanups * add hook to wp_update_user * update readme --------- Co-authored-by: Matthias Pfefferle --- README.md | 1 + includes/class-activity-dispatcher.php | 65 ++++++++++++++++---- includes/class-scheduler.php | 83 ++++++++++++++++++++++++++ readme.txt | 1 + 4 files changed, 137 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 656b282..da17996 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Added: CSS class for ActivityPub comments to allow custom designs * Added: FEP-2677: Identifying the Application Actor * Added: Basic Comment Federation +* Added: Profile Update Activities * Improved: WebFinger endpoints ### 1.3.0 ### diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index 1d6f353..99aa960 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -28,6 +28,7 @@ class Activity_Dispatcher { public static function init() { \add_action( 'activitypub_send_activity', array( self::class, 'send_activity' ), 10, 2 ); \add_action( 'activitypub_send_activity', array( self::class, 'send_activity_or_announce' ), 10, 2 ); + \add_action( 'activitypub_send_update_profile_activity', array( self::class, 'send_profile_update' ), 10, 1 ); } /** @@ -76,17 +77,7 @@ class Activity_Dispatcher { $activity = $transformer->to_activity( $type ); - $follower_inboxes = Followers::get_inboxes( $user_id ); - $mentioned_inboxes = Mention::get_inboxes( $activity->get_cc() ); - - $inboxes = array_merge( $follower_inboxes, $mentioned_inboxes ); - $inboxes = array_unique( $inboxes ); - - $json = $activity->to_json(); - - foreach ( $inboxes as $inbox ) { - safe_remote_post( $inbox, $json, $user_id ); - } + self::send_activity_to_inboxes( $activity, $user_id ); } /** @@ -112,12 +103,60 @@ class Activity_Dispatcher { $user_id = $transformer->get_wp_user_id(); $activity = $transformer->to_activity( 'Announce' ); - $follower_inboxes = Followers::get_inboxes( $user_id ); - $mentioned_inboxes = Mention::get_inboxes( $activity->get_cc() ); + self::send_activity_to_inboxes( $activity, $user_id ); + } + + /** + * Send a "Update" Activity when a user updates their profile. + * + * @param int $user_id The user ID to send an update for. + * + * @return void + */ + public static function send_profile_update( $user_id ) { + $user = Users::get_by_various( $user_id ); + + // bail if that's not a good user + if ( is_wp_error( $user ) ) { + return; + } + + // build the update + $activity = new Activity(); + $activity->set_id( $user->get_url() . '#update' ); + $activity->set_type( 'Update' ); + $activity->set_actor( $user->get_url() ); + $activity->set_object( $user->get_url() ); + $activity->set_to( 'https://www.w3.org/ns/activitystreams#Public' ); + + // send the update + self::send_activity_to_inboxes( $activity, $user_id ); + } + + /** + * Send an Activity to all followers and mentioned users. + * + * @param Activity $activity The ActivityPub Activity. + * @param int $user_id The user ID. + * + * @return void + */ + private static function send_activity_to_inboxes( $activity, $user_id ) { + $follower_inboxes = Followers::get_inboxes( $user_id ); + + $mentioned_inboxes = array(); + $cc = $activity->get_cc(); + if ( $cc ) { + $mentioned_inboxes = Mention::get_inboxes( $cc ); + } $inboxes = array_merge( $follower_inboxes, $mentioned_inboxes ); $inboxes = array_unique( $inboxes ); + if ( empty( $inboxes ) ) { + return; + } + $json = $activity->to_json(); foreach ( $inboxes as $inbox ) { diff --git a/includes/class-scheduler.php b/includes/class-scheduler.php index 61deab7..60e3f00 100644 --- a/includes/class-scheduler.php +++ b/includes/class-scheduler.php @@ -6,12 +6,15 @@ use Activitypub\Collection\Users; use Activitypub\Collection\Followers; use Activitypub\Transformer\Post; +use function Activitypub\is_user_type_disabled; + /** * ActivityPub Scheduler Class * * @author Matthias Pfefferle */ class Scheduler { + /** * Initialize the class, registering WordPress hooks */ @@ -58,6 +61,22 @@ class Scheduler { // Migration \add_action( 'admin_init', array( self::class, 'schedule_migration' ) ); + + // profile updates for blog options + if ( ! is_user_type_disabled( 'blog' ) ) { + \add_action( 'update_option_site_icon', array( self::class, 'blog_user_update' ) ); + \add_action( 'update_option_blogdescription', array( self::class, 'blog_user_update' ) ); + \add_action( 'update_option_blogname', array( self::class, 'blog_user_update' ) ); + \add_filter( 'pre_set_theme_mod_custom_logo', array( self::class, 'blog_user_update' ) ); + \add_filter( 'pre_set_theme_mod_header_image', array( self::class, 'blog_user_update' ) ); + } + + // profile updates for user options + if ( ! is_user_type_disabled( 'user' ) ) { + \add_action( 'wp_update_user', array( self::class, 'user_update' ) ); + \add_action( 'updated_user_meta', array( self::class, 'user_meta_update' ), 10, 3 ); + // @todo figure out a feasible way of updating the header image since it's not unique to any user. + } } /** @@ -255,4 +274,68 @@ class Scheduler { \wp_schedule_single_event( \time(), 'activitypub_schedule_migration' ); } } + + /** + * Send a profile update when relevant user meta is updated. + * + * @param int $meta_id Meta ID being updated. + * @param int $user_id User ID being updated. + * @param string $meta_key Meta key being updated. + * + * @return void + */ + public static function user_meta_update( $meta_id, $user_id, $meta_key ) { + // don't bother if the user can't publish + if ( ! \user_can( $user_id, 'publish_posts' ) ) { + return; + } + // the user meta fields that affect a profile. + $fields = array( + 'activitypub_user_description', + 'description', + 'user_url', + 'display_name', + ); + if ( in_array( $meta_key, $fields, true ) ) { + self::schedule_profile_update( $user_id ); + } + } + + /** + * Send a profile update when a user is updated. + * + * @param int $user_id User ID being updated. + * + * @return void + */ + public static function user_update( $user_id ) { + // don't bother if the user can't publish + if ( ! \user_can( $user_id, 'publish_posts' ) ) { + return; + } + + self::schedule_profile_update( $user_id ); + } + + /** + * Theme mods only have a dynamic filter so we fudge it like this. + * @param mixed $value + * @return mixed + */ + public static function blog_user_update( $value = null ) { + self::schedule_profile_update( 0 ); + return $value; + } + + /** + * Send a profile update to all followers. Gets hooked into all relevant options/meta etc. + * @param int $user_id The user ID to update (Could be 0 for Blog-User). + */ + public static function schedule_profile_update( $user_id ) { + \wp_schedule_single_event( + \time(), + 'activitypub_send_update_profile_activity', + array( $user_id ) + ); + } } diff --git a/readme.txt b/readme.txt index 510a3ca..f31f3ab 100644 --- a/readme.txt +++ b/readme.txt @@ -114,6 +114,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Added: CSS class for ActivityPub comments to allow custom designs * Added: FEP-2677: Identifying the Application Actor * Added: Basic Comment Federation +* Added: Profile Update Activities * Improved: WebFinger endpoints = 1.3.0 =