diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index 64ee03b..7cd3329 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -18,6 +18,7 @@ class Activity_Dispatcher { \add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity' ) ); \add_action( 'activitypub_send_comment_activity', array( '\Activitypub\Activity_Dispatcher', 'send_comment_activity' ) ); + \add_action( 'activitypub_send_update_comment_activity', array( '\Activitypub\Activity_Dispatcher', 'send_update_comment_activity' ) ); \add_action( 'activitypub_inbox_forward_activity', array( '\Activitypub\Activity_Dispatcher', 'inbox_forward_activity' ) ); \add_action( 'activitypub_send_delete_comment_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_comment_activity' ) ); } @@ -48,8 +49,14 @@ class Activity_Dispatcher { * @param \Activitypub\Model\Post $activitypub_post */ public static function send_update_activity( $activitypub_post ) { + // save permalink for delete + $post_id = \url_to_postid( $activitypub_post->get_id() ); + //shouldn't this go in schedule_*_activity? yeah + \update_post_meta( $post_id, '_ap_deleted_slug', $activitypub_post->get_id() ); + // get latest version of post $user_id = $activitypub_post->get_post_author(); + $updated = \wp_date( 'Y-m-d\TH:i:s\Z', \strtotime( $activitypub_post->get_updated() ) ); $activitypub_activity = new \Activitypub\Model\Activity( 'Update', \Activitypub\Model\Activity::TYPE_FULL ); $activitypub_activity->from_post( $activitypub_post->to_array() ); @@ -70,14 +77,17 @@ class Activity_Dispatcher { public static function send_delete_activity( $activitypub_post ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); + $deleted = \current_time( 'Y-m-d\TH:i:s\Z', true ); + $activitypub_post->set_deleted( $deleted ); $activitypub_activity = new \Activitypub\Model\Activity( 'Delete', \Activitypub\Model\Activity::TYPE_FULL ); $activitypub_activity->from_post( $activitypub_post->to_array() ); + $activitypub_activity->set_deleted( $deleted ); foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { $activitypub_activity->set_to( $to ); $activity = $activitypub_activity->to_json(); // phpcs:ignore - + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } } @@ -91,43 +101,31 @@ class Activity_Dispatcher { //ONLY FOR LOCAL USERS ? $activitypub_comment = \get_comment( $activitypub_comment_id ); $user_id = $activitypub_comment->user_id; - $replyto = get_comment_meta( $activitypub_comment->comment_parent, 'comment_author_url', true );// - $mentions = get_comment_meta( $activitypub_comment_id, 'mentions', true );// - //error_log( 'dispatcher:send_comment:$activitypub_comment: ' . print_r( $activitypub_comment, true ) ); + $replyto = get_comment_meta( $activitypub_comment->comment_parent, 'comment_author_url', true );// must include in replyto + $mentions = get_comment_meta( $activitypub_comment_id, 'mentions', true );//might be tagged $activitypub_comment = new \Activitypub\Model\Comment( $activitypub_comment ); $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL ); $activitypub_activity->from_comment( $activitypub_comment->to_array() ); - \error_log( 'Activity_Dispatcher::send_comment_activity: ' . print_r($activitypub_activity, true)); - foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { - \error_log( '$user_id: ' . $user_id . ', $inbox: '. $inbox . ', $to: '. print_r($to, true ) ); - $activitypub_activity->set_to( $to[0] ); + foreach ( \Activitypub\get_mentioned_inboxes( $mentions ) as $inbox => $to ) { + $activitypub_activity->set_to( $to );//all users at shared inbox + $activity = $activitypub_activity->to_json(); // phpcs:ignore + + \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); + } + //will this reset the activities? + + foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $cc ) { + $activitypub_activity->set_cc( $cc );//set_cc $activity = $activitypub_activity->to_json(); // phpcs:ignore // Send reply to followers, skip if replying to followers (avoid duplicate replies) - // if( in_array( $to, $replyto ) || ( $replyto == $to ) ) { - // break; - // } + if( in_array( $cc, $replyto ) || in_array( $cc, $mentions ) ) { + continue; + } \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } - // TODO: Reply (to followers and non-followers) - // if( is_array( $replyto ) && count( $replyto ) > 1 ) { - // foreach ( $replyto as $to ) { - // $inbox = \Activitypub\get_inbox_by_actor( $to ); - // $activitypub_activity->set_to( $to ); - // $activity = $activitypub_activity->to_json(); // phpcs:ignore - // error_log( 'dispatches->replyto: ' . $to ); - // \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); - // } - // } elseif ( !is_array( $replyto ) ) { - // $inbox = \Activitypub\get_inbox_by_actor( $to ); - // $activitypub_activity->set_to( $replyto ); - // $activity = $activitypub_activity->to_json(); // phpcs:ignore - // error_log( 'dispatch->replyto: ' . $replyto ); - // \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); - // } - } /** @@ -136,7 +134,6 @@ class Activity_Dispatcher { * @param \Activitypub\Model\Comment $activitypub_comment */ public static function inbox_forward_activity( $activitypub_comment_id ) { - //\error_log( 'Activity_Dispatcher::inbox_forward_activity' . print_r( $activitypub_comment, true ) ); $activitypub_comment = \get_comment( $activitypub_comment_id ); //original author should NOT recieve a copy of ther own post @@ -157,28 +154,17 @@ class Activity_Dispatcher { unset($activitypub_activity['user_id']); foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { - \error_log( '$user_id: ' . $user_id . ', $inbox: '. $inbox . ', $to: '. print_r($to, true ) ); //Forward reply to followers, skip sender if( in_array( $to, $replyto ) || ( $replyto == $to ) ) { - error_log( 'dispatch:forward: nope:' . print_r( $to, true ) ); - break; + continue; } $activitypub_activity['object']['to'] = $to; $activitypub_activity['to'] = $to; - //$activitypub_activity - //$activitypub_activity->set_to( $to ); - //$activity = $activitypub_activity->to_json(); // phpcs:ignore - $activity = \wp_json_encode( $activitypub_activity, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT ); - error_log( 'dispatch:forward:activity:' . print_r( $activity, true ) ); \Activitypub\forward_remote_post( $inbox, $activity, $user_id ); - - //reset //unnecessary - //array_pop( $activitypub_activity->object->to[] ); - //array_pop( $activitypub_activity->to[] ); } } @@ -190,16 +176,23 @@ class Activity_Dispatcher { public static function send_delete_comment_activity( $activitypub_comment_id ) { // get comment $activitypub_comment = \get_comment( $activitypub_comment_id ); - $user_id = $activitypub_comment->post_author; + $user_id = $activitypub_comment->user_id; + // Prevent sending received/anonymous comments + if ( ! $user_id ) { + return; + } + + $deleted = \wp_date( 'Y-m-d\TH:i:s\Z', \strtotime( $activitypub_comment->comment_date_gmt ) ); $activitypub_comment = new \Activitypub\Model\Comment( $activitypub_comment ); + $activitypub_comment->set_deleted( $deleted ); $activitypub_activity = new \Activitypub\Model\Activity( 'Delete', \Activitypub\Model\Activity::TYPE_FULL ); $activitypub_activity->from_comment( $activitypub_comment->to_array() ); + $activitypub_activity->set_deleted( $deleted ); foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { $activitypub_activity->set_to( $to ); $activity = $activitypub_activity->to_json(); // phpcs:ignore - \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); } } diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index 6393aef..d81e8e9 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -27,7 +27,10 @@ class Activitypub { \add_filter( 'preprocess_comment' , array( '\Activitypub\Activitypub', 'preprocess_comment' ) ); \add_filter( 'comment_post' , array( '\Activitypub\Activitypub', 'postprocess_comment' ), 10, 3 ); + \add_filter( 'wp_update_comment_data', array( '\Activitypub\Activitypub', 'comment_updated_published' ), 20, 3 ); \add_action( 'transition_comment_status', array( '\Activitypub\Activitypub', 'schedule_comment_activity' ), 20, 3 ); + \add_action( 'edit_comment', array( '\Activitypub\Activitypub', 'edit_comment' ), 20, 2 );//schedule_admin_comment_activity + \add_filter( 'get_comment_text', array( '\Activitypub\Activitypub', 'comment_append_edit_datetime' ), 10, 3 ); } @@ -39,12 +42,16 @@ class Activitypub { * @return string The new path to the JSON template. */ public static function render_json_template( $template ) { - if ( ! \is_author() && ! \is_singular() && ! \is_home() ) { + if ( ! \is_author() && ! \is_singular() && ! \is_home() && ! \Activitypub\is_ap_comment() ) { return $template; } if ( \is_author() ) { $json_template = \dirname( __FILE__ ) . '/../templates/author-json.php'; + } elseif ( \Activitypub\is_ap_replies() ) { + $json_template = \dirname( __FILE__ ) . '/../templates/replies-json.php'; + } elseif ( \Activitypub\is_ap_comment() ) { + $json_template = \dirname( __FILE__ ) . '/../templates/comment-json.php'; } elseif ( \is_singular() ) { $json_template = \dirname( __FILE__ ) . '/../templates/post-json.php'; } elseif ( \is_home() ) { @@ -90,6 +97,12 @@ class Activitypub { */ public static function add_query_vars( $vars ) { $vars[] = 'activitypub'; + $vars[] = 'ap_comment_id';//comment_id doesn't work, 'c' is probably too short and prone to collisions + + //Collections review + $vars[] = 'replies'; + $vars[] = 'collection_page'; + $vars[] = 'only_other_accounts'; return $vars; } @@ -124,44 +137,35 @@ class Activitypub { if ( 'publish' === $new_status && 'publish' !== $old_status ) { \wp_schedule_single_event( \time(), 'activitypub_send_post_activity', array( $activitypub_post ) ); - } elseif ( 'publish' === $new_status ) { + } elseif ( 'publish' === $new_status ) { //this triggers when restored post from trash, which may not be desired \wp_schedule_single_event( \time(), 'activitypub_send_update_activity', array( $activitypub_post ) ); } elseif ( 'trash' === $new_status ) { \wp_schedule_single_event( \time(), 'activitypub_send_delete_activity', array( $activitypub_post ) ); } } - /** + /** * preprocess local comments for federated replies */ public static function preprocess_comment( $commentdata ) { - - //must only process replies from local actors + // only process replies from local actors if ( !empty( $commentdata['user_id'] ) ) { - //\error_log( 'is_local user' );//TODO Test - //TODO TEST - $post_type = \get_object_subtype( 'post', $commentdata['comment_post_ID'] ); - $ap_post_types = \get_option( 'activitypub_support_post_types' ); - if ( !\is_null( $ap_post_types ) ) { - if ( in_array( $post_type, $ap_post_types ) ) { - $commentdata['comment_type'] = 'activitypub'; - // transform webfinger mentions to links and add @mentions to cc - $tagged_content = \Activitypub\transform_tags( $commentdata['comment_content'] ); - $commentdata['comment_content'] = $tagged_content['content']; - $commentdata['comment_meta']['mentions'] = $tagged_content['mentions']; - } - } + $commentdata['comment_type'] = 'activitypub'; + // transform webfinger mentions to links and add @mentions to cc + $tagged_content = \Activitypub\transform_tags( $commentdata['comment_content'] ); + $commentdata['comment_content'] = $tagged_content['content']; + $commentdata['comment_meta']['mentions'] = $tagged_content['mentions']; } - return $commentdata; - } + return $commentdata; + } /** + * comment_post() * postprocess_comment for federating replies and inbox-forwarding */ public static function postprocess_comment( $comment_id, $comment_approved, $commentdata ) { //Admin users comments bypass transition_comment_status (auto approved) - //\error_log( 'postprocess_comment_handler: comment_status: ' . $comment_approved ); if ( $commentdata['comment_type'] === 'activitypub' ) { if ( ( $comment_approved === 1 ) && @@ -176,7 +180,7 @@ class Activitypub { } else { // TODO check that this is unused - // TODO comment test as anon + // TODO comment test as anon / no auth_url, no fetchable status? // TODO comment test as registered // TODO comment test as anyother site settings @@ -197,14 +201,27 @@ class Activitypub { } } } - + + /** + * edit_comment() + * + * Fires immediately after a comment is updated in the database. + * Fires immediately before comment status transition hooks are fired. (useful only for admin) + */ + public static function edit_comment( $comment_ID, $data ) { + // advantage of ap_published is it would be set once, (does preprocess fire on edit?) + if ( ! is_null( $data['user_id'] ) ) { + \wp_schedule_single_event( \time(), 'activitypub_send_update_comment_activity', array( $comment_ID ) ); + } + } + /** * Schedule Activities - * + * + * transition_comment_status() * @param int $comment */ public static function schedule_comment_activity( $new_status, $old_status, $activitypub_comment ) { - // TODO format $activitypub_comment = new \Activitypub\Model\Comment( $comment ); if ( 'approved' === $new_status && 'approved' !== $old_status ) { //should only federate replies from local actors @@ -222,17 +239,35 @@ class Activitypub { || in_array( $local_user, $ap_object['tag'] ) ) { //if inReplyTo, object, target and/or tag are (local-wp) objects - //\ActivityPub\Activity_Dispatcher::inbox_forward_activity( $activitypub_comment ); \wp_schedule_single_event( \time(), 'activitypub_inbox_forward_activity', array( $activitypub_comment->comment_ID ) ); } } } } elseif ( 'trash' === $new_status ) { - \wp_schedule_single_event( \time(), 'activitypub_send_delete_comment_activity', array( $activitypub_comment ) ); + \wp_schedule_single_event( \time(), 'activitypub_send_delete_comment_activity', array( $activitypub_comment ) ); + } elseif ( $old_status === $new_status ) { + //TODO Test with non-admin user + \wp_schedule_single_event( \time(), 'activitypub_send_update_comment_activity', array( $activitypub_comment->comment_ID ) ); } else { + //error_log( 'schedule_update_comment_activity: else?:' ); } } - + + /** + * get_comment_text( $comment ) + * + * Filters the comment content before it is updated in the database. + */ + public static function comment_append_edit_datetime( $comment_text, $comment, $args ) { + if ( 'activitypub' === $comment->comment_type ) { + $updated = \wp_date( 'Y-m-d H:i:s', \strtotime( \get_comment_meta( $comment->comment_ID, 'ap_last_modified', true ) ) ); + if( $updated ) { + $append_updated = "