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' );
+ }
+}