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/activitypub.php b/activitypub.php index 55d040b..3ec6cb5 100644 --- a/activitypub.php +++ b/activitypub.php @@ -33,6 +33,7 @@ require_once __DIR__ . '/includes/functions.php'; \defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "[ap_title]\n\n[ap_content]\n\n[ap_hashtags]\n\n[ap_shortlink]" ); \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) || \define( 'ACTIVITYPUB_AUTHORIZED_FETCH', false ); \defined( 'ACTIVITYPUB_DISABLE_REWRITES' ) || \define( 'ACTIVITYPUB_DISABLE_REWRITES', false ); +\defined( 'ACTIVITYPUB_SHARED_INBOX_FEATURE' ) || \define( 'ACTIVITYPUB_SHARED_INBOX_FEATURE', false ); \define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); \define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index 7665425..6bb6a04 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 ); } /** @@ -74,19 +75,9 @@ class Activity_Dispatcher { return; } - $activity = $transformer->to_activity( 'Create' ); + $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-admin.php b/includes/class-admin.php index 7acfff9..dca3aee 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -18,6 +18,7 @@ class Admin { \add_action( 'admin_init', array( self::class, 'register_settings' ) ); \add_action( 'personal_options_update', array( self::class, 'save_user_description' ) ); \add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_scripts' ) ); + \add_action( 'admin_notices', array( self::class, 'admin_notices' ) ); if ( ! is_user_disabled( get_current_user_id() ) ) { \add_action( 'show_user_profile', array( self::class, 'add_profile' ) ); @@ -46,6 +47,37 @@ class Admin { } } + /** + * Display admin menu notices about configuration problems or conflicts. + * + * @return void + */ + public static function admin_notices() { + $permalink_structure = \get_option( 'permalink_structure' ); + if ( empty( $permalink_structure ) ) { + $admin_notice = \__( 'You are using the ActivityPub plugin without setting a permalink structure. This will prevent ActivityPub from working. Please set a permalink structure.', 'activitypub' ); + self::show_admin_notice( $admin_notice, 'error' ); + } + } + + /** + * Display one admin menu notice about configuration problems or conflicts. + * + * @param string $admin_notice The notice to display. + * @param string $level The level of the notice (error, warning, success, info). + * + * @return void + */ + private static function show_admin_notice( $admin_notice, $level ) { + ?> + +
+

+
+ + get__id(); + + // save follower + $follower = Followers::add_follower( + $user_id, + $activity['actor'] + ); + + do_action( + 'activitypub_followers_post_follow', + $activity['actor'], + $activity, + $user_id, + $follower + ); } /** diff --git a/includes/handler/class-undo.php b/includes/handler/class-undo.php index 13c06f3..74d3dca 100644 --- a/includes/handler/class-undo.php +++ b/includes/handler/class-undo.php @@ -1,6 +1,7 @@ get__id(); + Followers::remove_follower( $user_id, $activity['actor'] ); } } diff --git a/includes/handler/class-update.php b/includes/handler/class-update.php index 00e0430..002c3d5 100644 --- a/includes/handler/class-update.php +++ b/includes/handler/class-update.php @@ -14,7 +14,10 @@ class Update { * Initialize the class, registering WordPress hooks */ public static function init() { - \add_action( 'activitypub_inbox_update', array( self::class, 'handle_update' ), 10, 2 ); + \add_action( + 'activitypub_inbox_update', + array( self::class, 'handle_update' ) + ); } /** @@ -23,7 +26,7 @@ class Update { * @param array $array The activity-object * @param int $user_id The id of the local blog-user */ - public static function handle_update( $array, $user_id ) { + public static function handle_update( $array ) { $object_type = isset( $array['object']['type'] ) ? $array['object']['type'] : ''; switch ( $object_type ) { @@ -45,7 +48,7 @@ class Update { case 'Video': case 'Event': case 'Document': - self::update_interaction( $array, $user_id ); + self::update_interaction( $array ); break; // Minimal Activity // @see https://www.w3.org/TR/activitystreams-core/#example-1 @@ -62,7 +65,7 @@ class Update { * * @return void */ - public static function update_interaction( $activity, $user_id ) { + public static function update_interaction( $activity ) { $state = Interactions::update_comment( $activity ); $reaction = null; @@ -70,7 +73,7 @@ class Update { $reaction = \get_comment( $state ); } - \do_action( 'activitypub_handled_update', $activity, $user_id, $state, $reaction ); + \do_action( 'activitypub_handled_update', $activity, null, $state, $reaction ); } /** diff --git a/includes/model/class-user.php b/includes/model/class-user.php index c713434..c22f83c 100644 --- a/includes/model/class-user.php +++ b/includes/model/class-user.php @@ -226,6 +226,18 @@ class User extends Actor { return get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $this->get__id() ) ); } + public function get_endpoints() { + $endpoints = null; + + if ( ACTIVITYPUB_SHARED_INBOX_FEATURE ) { + $endpoints = array( + 'sharedInbox' => get_rest_url_by_path( 'inbox' ), + ); + } + + return $endpoints; + } + /** * Extend the User-Output with Attachments. * diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 938ca90..c527040 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -94,11 +94,8 @@ class Inbox { $json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' ); $json->type = 'OrderedCollectionPage'; $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/inbox', $user->get__id() ) ); // phpcs:ignore - $json->totalItems = 0; // phpcs:ignore - $json->orderedItems = array(); // phpcs:ignore - $json->first = $json->partOf; // phpcs:ignore // filter output @@ -155,37 +152,10 @@ class Inbox { $data = $request->get_json_params(); $activity = Activity::init_from_array( $data ); $type = $request->get_param( 'type' ); - $users = self::get_recipients( $data ); + $type = \strtolower( $type ); - if ( ! $users ) { - return new WP_Error( - 'rest_invalid_param', - \__( 'No recipients found', 'activitypub' ), - array( - 'status' => 400, - 'params' => array( - 'to' => \__( 'Please check/validate "to" field', 'activitypub' ), - 'bto' => \__( 'Please check/validate "bto" field', 'activitypub' ), - 'cc' => \__( 'Please check/validate "cc" field', 'activitypub' ), - 'bcc' => \__( 'Please check/validate "bcc" field', 'activitypub' ), - 'audience' => \__( 'Please check/validate "audience" field', 'activitypub' ), - ), - ) - ); - } - - foreach ( $users as $user ) { - $user = User_Collection::get_by_various( $user ); - - if ( is_wp_error( $user ) ) { - continue; - } - - $type = \strtolower( $type ); - - \do_action( 'activitypub_inbox', $data, $user->ID, $type, $activity ); - \do_action( "activitypub_inbox_{$type}", $data, $user->ID, $activity ); - } + \do_action( 'activitypub_inbox', $data, null, $type, $activity ); + \do_action( "activitypub_inbox_{$type}", $data, null, $activity ); $rest_response = new WP_REST_Response( array(), 202 ); $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php index b5cdd8d..b5eba90 100644 --- a/includes/transformer/class-post.php +++ b/includes/transformer/class-post.php @@ -395,7 +395,7 @@ class Post extends Base { * * @return string The Object-Type. */ - protected function get_object_type() { + protected function get_type() { if ( 'wordpress-post-format' !== \get_option( 'activitypub_object_type', 'note' ) ) { return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) ); } 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 = diff --git a/tests/test-class-admin.php b/tests/test-class-admin.php new file mode 100644 index 0000000..73db3be --- /dev/null +++ b/tests/test-class-admin.php @@ -0,0 +1,18 @@ +expectOutputRegex( "/notice-error/" ); + + \delete_option( 'permalink_structure' ); + } + + public function test_has_permalink_structure_no_errors() { + \add_option( 'permalink_structure', '/archives/%post_id%' ); + \do_action( 'admin_notices' ); + $this->expectOutputRegex( "/^((?!notice-error).)*$/s" ); + + \delete_option( 'permalink_structure' ); + } +}