From 8e1c9ff6bb879ba7bf4bce728e2e09ab6438bdd2 Mon Sep 17 00:00:00 2001 From: Django Doucet Date: Tue, 27 Sep 2022 15:28:45 -0600 Subject: [PATCH] Replies Collection, settings, other fixes --- activitypub.php | 13 ++++ includes/class-activity-dispatcher.php | 39 ++++------ includes/class-activitypub.php | 10 +-- includes/class-admin.php | 39 ++++------ includes/functions.php | 61 ++++++--------- includes/model/class-comment.php | 103 ++++++++++++++----------- includes/model/class-post.php | 52 ++++++------- includes/rest/class-inbox.php | 15 +--- includes/rest/class-webfinger.php | 1 + templates/comment-json.php | 2 +- templates/replies-json.php | 84 ++++++++++++++++++++ 11 files changed, 246 insertions(+), 173 deletions(-) create mode 100644 templates/replies-json.php diff --git a/activitypub.php b/activitypub.php index b5aa607..c6a51fd 100644 --- a/activitypub.php +++ b/activitypub.php @@ -86,6 +86,19 @@ function init() { } \add_action( 'plugins_loaded', '\Activitypub\init' ); +/** + * Add plugin settings link + */ +function plugin_settings_link( $links ) { + $settings_link[] = \sprintf( '%2s', + \menu_page_url( 'activitypub', false ), + \__( 'Settings', 'activitypub' ) + ); + + return \array_merge( $settings_link, $links ); +} +\add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), '\Activitypub\plugin_settings_link' ); + /** * Add rewrite rules */ diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index 4d42c24..76d762d 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -15,7 +15,7 @@ class Activity_Dispatcher { public static function init() { \add_action( 'activitypub_send_post_activity', array( '\Activitypub\Activity_Dispatcher', 'send_post_activity' ) ); \add_action( 'activitypub_send_update_activity', array( '\Activitypub\Activity_Dispatcher', 'send_update_activity' ) ); - \add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity' ) ); + \add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity', 2 ) ); \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' ) ); @@ -49,11 +49,6 @@ 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() ) ); @@ -74,10 +69,13 @@ class Activity_Dispatcher { * * @param \Activitypub\Model\Post $activitypub_post */ - public static function send_delete_activity( $activitypub_post ) { + public static function send_delete_activity( $activitypub_post, $permalink = null ) { // get latest version of post $user_id = $activitypub_post->get_post_author(); $deleted = \current_time( 'Y-m-d\TH:i:s\Z', true ); + if ( $permalink ) { + $activitypub_post->set_id( $permalink ); + } $activitypub_post->set_deleted( $deleted ); $activitypub_activity = new \Activitypub\Model\Activity( 'Delete', \Activitypub\Model\Activity::TYPE_FULL ); @@ -101,27 +99,27 @@ 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 );// must include in replyto - $mentions = get_comment_meta( $activitypub_comment_id, 'mentions', true );//might be tagged + $mentions[] = \get_comment_meta( $activitypub_comment_id, 'mentions', true );// mention[href, name] $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() ); + $mentioned_actors = array(); 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 ); + + $mentioned_actors[] = $to; } - //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( $cc, $replyto ) || in_array( $cc, $mentions ) ) { + // Send reply to followers, skip if mentioned followers (avoid duplicate replies) + if ( in_array( $cc, $mentioned_actors ) ) { continue; } \Activitypub\safe_remote_post( $inbox, $activity, $user_id ); @@ -136,12 +134,11 @@ class Activity_Dispatcher { public static function inbox_forward_activity( $activitypub_comment_id ) { $activitypub_comment = \get_comment( $activitypub_comment_id ); - //original author should NOT recieve a copy of ther own post + //original author should NOT recieve a copy of their own post $replyto[] = $activitypub_comment->comment_author_url; $activitypub_activity = unserialize( get_comment_meta( $activitypub_comment->comment_ID, 'ap_object', true ) ); //will be forwarded to the parent_comment->author or post_author followers collection - //TODO verify that ... what? $parent_comment = \get_comment( $activitypub_comment->comment_parent ); if ( ! is_null( $parent_comment ) ) { $user_id = $parent_comment->user_id; @@ -150,18 +147,16 @@ class Activity_Dispatcher { $user_id = $original_post->post_author; } - //remove user_id from $activitypub_comment - unset( $activitypub_activity['user_id'] ); - - foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) { + unset( $activitypub_activity['user_id'] ); // remove user_id from $activitypub_comment + foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $cc ) { //Forward reply to followers, skip sender - if ( in_array( $to, $replyto ) || ( $replyto == $to ) ) { + if ( in_array( $cc, $replyto ) ) { continue; } - $activitypub_activity['object']['to'] = $to; - $activitypub_activity['to'] = $to; + $activitypub_activity['object']['cc'] = $cc; + $activitypub_activity['cc'] = $cc; $activity = \wp_json_encode( $activitypub_activity, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT ); \Activitypub\forward_remote_post( $inbox, $activity, $user_id ); diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index 9a26173..c3d1062 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -42,7 +42,7 @@ 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() && ! \Activitypub\is_ap_comment() ) { + if ( ! \is_author() && ! \is_singular() && ! \is_home() && ! \Activitypub\is_ap_comment() && ! \Activitypub\is_ap_replies() ) { return $template; } @@ -169,12 +169,11 @@ class Activitypub { if ( $commentdata['comment_type'] === 'activitypub' ) { if ( ( $comment_approved === 1 ) && ! empty( $commentdata['user_id'] ) && - ( $user = get_userdata( $commentdata['user_id'] ) ) && // get the user data - in_array( 'administrator', $user->roles ) // check the roles + ( $user = \get_userdata( $commentdata['user_id'] ) ) && // get the user data + \in_array( 'administrator', $user->roles ) // check the roles ) { - // Only for Admins? + // Only for Admins $mentions = \get_comment_meta( $comment_id, 'mentions', true ); - //\ActivityPub\Activity_Dispatcher::send_comment_activity( $comment_id ); // performance > followers collection \wp_schedule_single_event( \time(), 'activitypub_send_comment_activity', array( $comment_id ) ); } else { @@ -193,7 +192,6 @@ class Activitypub { * 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 ) ); } diff --git a/includes/class-admin.php b/includes/class-admin.php index 13e57c9..e88694b 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -164,44 +164,31 @@ class Admin { // Public Reply $reply_button = ''; $actions['reply'] = sprintf( - $reply_button, - $comment->comment_ID, - $comment->comment_post_ID, - 'replyto', - 'vim-r comment-inline', + $reply_button, + $comment->comment_ID, + $comment->comment_post_ID, + 'replyto', + 'vim-r comment-inline', esc_attr__( 'Reply to this comment' ), $recipients, $summary, - __( 'Reply', 'activitypub' ) + __( 'Reply', 'activitypub' ) ); - - // Private - // $actions['private_reply'] = sprintf( - // $format, - // $comment->comment_ID, - // $comment->comment_post_ID, - // 'private_replyto', - // 'vim-r comment-inline', - // esc_attr__( 'Reply in private to this comment' ), - // $recipients, - // $summary, - // __( 'Private reply', 'activitypub' ) - // ); return $actions; } public static function scripts_reply_comments( $hook ) { - if ('edit-comments.php' !== $hook) { + if ( 'edit-comments.php' !== $hook ) { return; } - wp_enqueue_script( 'activitypub_client', - plugin_dir_url(__FILE__) . '/activitypub.js', - array('jquery'), - filemtime( plugin_dir_path(__FILE__) . '/activitypub.js' ), - true + wp_enqueue_script( + 'activitypub_client', + plugin_dir_url( __FILE__ ) . '/activitypub.js', + array( 'jquery' ), + filemtime( plugin_dir_path( __FILE__ ) . '/activitypub.js' ), + true ); } - } diff --git a/includes/functions.php b/includes/functions.php index 13368f1..174f353 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -251,6 +251,12 @@ function get_follower_inboxes( $user_id ) { return $inboxes; } +/** + * + * @param $mentions array of mentioned actors, each mention is an array of actor URI (href), and webfinger (name) + * + * @return array of (shared) inboxes + */ function get_mentioned_inboxes( $mentions ) { $inboxes = array(); @@ -259,7 +265,7 @@ function get_mentioned_inboxes( $mentions ) { if ( ! $inbox || \is_wp_error( $inbox ) ) { continue; } - // init array if empty + if ( ! isset( $inboxes[ $inbox ] ) ) { $inboxes[ $inbox ] = array(); } @@ -355,7 +361,10 @@ function url_to_authorid( $url ) { /** * Verify if in_replyto_url is a local comment, * Or if it is a previously received remote comment - * return int comment_id + * (For threading comments locally) + * + * @param string activitypub object id URI + * @return int comment_id */ function url_to_commentid( $in_replyto_url ) { if ( empty( $in_replyto_url ) ) { @@ -364,16 +373,12 @@ function url_to_commentid( $in_replyto_url ) { //rewrite for activitypub object id simplification $url_maybe_id = \wp_parse_url( $in_replyto_url ); - - if ( site_url() === $url_maybe_id['scheme'] . '://' . $url_maybe_id['host'] ) { + if ( site_url() === $url_maybe_id['scheme'] . '://' . $url_maybe_id['host'] && !empty( $url_maybe_id['query'] )) { //is local post or comment \parse_str( $url_maybe_id['query'], $reply_query ); - if ( isset( $reply_query['ap_comment_id'] ) && is_int( $reply_query['ap_comment_id'] ) ) { + if ( isset( $reply_query['ap_comment_id'] ) ) { //is local comment return $reply_query['ap_comment_id']; - } else { - //not a comment - return null; } } else { //is remote url @@ -396,14 +401,14 @@ function url_to_commentid( $in_replyto_url ) { } return $found_comment_ids[0]; } - return null; } + return null; } /** * Verify if url is a wp_ap_comment, * Or if it is a previously received remote comment - * return int comment_id + * @return int comment_id */ function is_ap_comment() { $comment_id = get_query_var( 'ap_comment_id', null ); @@ -418,11 +423,10 @@ function is_ap_comment() { } /** - * Verify if url is a /replies endoint, - * return int true + * Verify if url has a replies query, + * @return bool */ function is_ap_replies() { - global $wp; $replies = get_query_var( 'replies' ); if ( $replies ) { return $replies; @@ -476,8 +480,10 @@ function get_summary( $comment_id ) { } /** - * parse content for tags to transform + * Parse content for tags to transform + * * @param string $content to search + * @return array content, mentions (for storage in post_meta) */ function transform_tags( $content ) { //#tags @@ -544,21 +550,8 @@ function url_to_webfinger( $user_url ) { } /** - * Transform comment url, replace #fragment with ?query - * - * AP Object ID must be unique - * - * https://www.w3.org/TR/activitypub/#obj-id - * https://github.com/tootsuite/mastodon/issues/13879 - */ -function normalize_comment_url( $comment ) { - $comment_id = explode( '#comment-', \get_comment_link( $comment ) ); - $comment_id = $comment_id[0] . '?ap_comment_id=' . $comment_id[1]; - return $comment_id; -} - -/** - * Set ap_comment_id + * @param $comment or $comment_id + * @return ActivityPub URI of comment * * AP Object ID must be unique * @@ -566,20 +559,16 @@ function normalize_comment_url( $comment ) { * https://github.com/tootsuite/mastodon/issues/13879 */ function set_ap_comment_id( $comment ) { + $comment = \get_comment( $comment ); $ap_comment_id = add_query_arg( array( 'p' => $comment->comment_post_ID, - 'ap_comment_id' => $comment->comment_ID, //should probably rename to ap_comment or something + 'ap_comment_id' => $comment->comment_ID, ), trailingslashit( site_url() ) ); return $ap_comment_id; } -/* comment_id_to_url( $comment_id ) { - //get remote from post_id from comment meta - //get local normalized comment_link - } -*/ /** * Determine AP audience of incoming object @@ -591,7 +580,7 @@ function get_audience( $object ) { return 'public'; } if ( in_array( AS_PUBLIC, $object['cc'] ) ) { - return 'unlisted';//is unlisted even relevant? + return 'unlisted'; } if ( ! in_array( AS_PUBLIC, $object['to'] ) && ! in_array( AS_PUBLIC, $object['cc'] ) ) { $author_post_url = get_author_posts_url( $object['user_id'] ); diff --git a/includes/model/class-comment.php b/includes/model/class-comment.php index cd5802d..2ef5e1d 100644 --- a/includes/model/class-comment.php +++ b/includes/model/class-comment.php @@ -23,7 +23,7 @@ class Comment { $this->contentWarning = $this->generate_content_warning(); $this->permalink = $this->generate_permalink(); $this->context = $this->generate_context(); - $this->cc_recipients = $this->generate_recipients(); + $this->to_recipients = $this->generate_mention_recipients(); $this->tags = $this->generate_tags(); $this->update = $this->generate_update(); $this->deleted = $this->generate_trash(); @@ -59,9 +59,10 @@ class Comment { 'context' => $this->context, //'source' => \get_comment_link( $comment ), //non-conforming, see https://www.w3.org/TR/activitypub/#source-property 'url' => \get_comment_link( $comment ), //link for mastodon - 'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ), //audience logic - 'cc' => $this->cc_recipients, + 'to' => $this->to_recipients, + 'cc' => array( 'https://www.w3.org/ns/activitystreams#Public' ), 'tag' => $this->tags, + 'replies' => $this->replies, ); if ( $this->replies ) { $array['replies'] = $this->replies; @@ -85,7 +86,7 @@ class Comment { } public function generate_comment_id() { - return \Activitypub\set_ap_comment_id( $this->comment ); + return \Activitypub\set_ap_comment_id( $this->comment->comment_ID ); } public function generate_permalink() { @@ -96,7 +97,7 @@ class Comment { } /** - * What is status is being replied to + * What is status being replied to * Comment ID or Post ID */ public function generate_parent_url() { @@ -114,31 +115,43 @@ class Comment { trailingslashit( site_url() ) ); } - } else { - $inReplyTo = add_query_arg( - array( - 'p' => $comment->comment_post_ID, - ), - trailingslashit( site_url() ) - ); + } else { //parent is_post + // Backwards compatibility + $pretty_permalink = \get_post_meta( $comment->comment_post_ID, '_activitypub_permalink_compat', true ); // TODO finalize meta + if ( $pretty_permalink ) { + $inReplyTo = $pretty_permalink; + } else { + $inReplyTo = add_query_arg( + array( + 'p' => $comment->comment_post_ID, + ), + trailingslashit( site_url() ) + ); + } } return $inReplyTo; } public function generate_context() { $comment = $this->comment; - $inReplyTo = add_query_arg( - array( - 'p' => $comment->comment_post_ID, - ), - trailingslashit( site_url() ) - ); - return $inReplyTo; + // support pretty_permalinks + $pretty_permalink = \get_post_meta( $comment->comment_post_ID, '_activitypub_permalink_compat', true ); + if ( $pretty_permalink ) { + $context = $pretty_permalink; + } else { + $context = add_query_arg( + array( + 'p' => $comment->comment_post_ID, + ), + trailingslashit( site_url() ) + ); + } + return $context; } /** * Generate courtesy Content Warning - * If peer used CW let's just copy it + * If parent status used CW let's just copy it * TODO: Move to preprocess_comment / row_actions * Add option for wrapping CW in Details/Summary markup * Figure out some CW syntax: [shortcode-style], {brackets-style}? @@ -148,7 +161,7 @@ class Comment { $comment = $this->comment; $contentWarning = null; - // TODO Replace auto CW, with Title field or CW shortcode + // Temporarily generate Summary from parent $parent_comment = \get_comment( $comment->comment_parent ); if ( $parent_comment ) { //get (received) comment @@ -157,9 +170,10 @@ class Comment { $contentWarning = $ap_object['object']['summary']; } } + // TODO Replace auto generate with Summary shortcode /*summary = \get_comment_meta( $this->comment->comment_ID, 'summary', true ) ; if ( !empty( $summary ) ) { - $contentWarning = \Activitypub\add_summary( $summary ); //TODO + $contentWarning = \Activitypub\add_summary( $summary ); } */ return $contentWarning; } @@ -167,9 +181,7 @@ class Comment { /** * Who is being replied to */ - public function generate_recipients() { - //TODO Add audience logic get parent audience - //TODO shouldn't mentions go in 'to'? + public function generate_mention_recipients() { $recipients = array( AS_PUBLIC ); $mentions = \get_comment_meta( $this->comment->comment_ID, 'mentions', true ); if ( ! empty( $mentions ) ) { @@ -226,40 +238,41 @@ class Comment { */ public function generate_replies() { $comment = $this->comment; + $replies = []; $args = array( 'post_id' => $comment->comment_post_ID, 'parent' => $comment->comment_ID, - 'author__in' => $comment->user_id, 'status' => 'approve', 'hierarchical' => false, ); - $children = \get_comments( $args ); - $replies = null; - if ( $children ) { + $comments_list = \get_comments( $args ); + + if ( $comments_list ) { $items = array(); - foreach ( $children as $child_comment ) { - $comment_url = \add_query_arg( - array( - 'p' => $child_comment->comment_post_ID, - 'ap_comment_id' => $child_comment->comment_ID, - ), - trailingslashit( site_url() ) - ); - $items[] = $comment_url; + foreach ( $comments_list as $comment ) { + // remote replies + $source_url = \get_comment_meta( $comment->comment_ID, 'source_url', true ); + if ( ! empty( $source_url ) ){ + $items[] = $source_url; + } else { + // local replies + $comment_url = \add_query_arg( // + array( + 'p' => $comment->comment_post_ID, + 'ap_comment_id' => $comment->comment_ID, + ), + trailingslashit( site_url() ) + ); + $items[] = $comment_url; + } } + $replies = (object) array( 'type' => 'Collection', 'id' => \add_query_arg( array( 'replies' => '' ), $this->id ), 'first' => (object) array( 'type' => 'CollectionPage', 'partOf' => \add_query_arg( array( 'replies' => '' ), $this->id ), - 'next' => \add_query_arg( - array( - 'replies' => '', - 'page' => 1, - ), - $this->id - ), 'items' => $items, ), ); diff --git a/includes/model/class-post.php b/includes/model/class-post.php index 0bae523..c16e2e0 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -30,9 +30,7 @@ class Post { $this->tags = $this->generate_tags(); $this->object_type = $this->generate_object_type(); $this->replies = $this->generate_replies(); - //$this->updated = $this->generate_updated(); - $this->slug = \get_permalink( $this->id ); - $this->updated = null; + $this->updated = $this->generate_updated(); $this->delete = $this->get_deleted(); } @@ -73,9 +71,10 @@ class Post { } if ( $this->deleted ) { $array['deleted'] = \date( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_modified_gmt ) ); - // TODO if using slugs instead of ids _ap_deleted_slug - //$deleted_post_slug = \get_post_meta( $post->ID, '_ap_deleted_slug', true ); - //$array['id'] = $deleted_post_slug; + $deleted_post_slug = \get_post_meta( $post->ID, '_activitypub_permalink_compat', true ); + if ( $deleted_post_slug ) { + $array['id'] = $deleted_post_slug; + } } if ( $this->updated ) { $array['updated'] = \date( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_modified_gmt ) ); @@ -89,15 +88,19 @@ class Post { public function generate_id() { $post = $this->post; - $permalink = \add_query_arg( // - array( - 'p' => $post->ID, - ), - trailingslashit( site_url() ) - ); + $pretty_permalink = \get_post_meta( $post->ID, '_activitypub_permalink_compat', true ); - // replace 'trashed' for delete activity - return \str_replace( '__trashed', '', $permalink ); + if( ! empty( $pretty_permalink ) ) { + $object_id = $pretty_permalink; + } else { + $object_id = \add_query_arg( // + array( + 'p' => $post->ID, + ), + trailingslashit( site_url() ) + ); + } + return $object_id; } public function generate_attachments() { @@ -180,10 +183,9 @@ class Post { public function generate_replies() { $replies = null; - //\error_log( 'generate_replies: $post' . print_r( $this->post, true ) ); if ( $this->post->comment_count > 0 ) { $args = array( - 'post_id' => $this->post->ID, // Use post_id, not post_ID + 'post_id' => $this->post->ID, 'hierarchical' => false, 'status' => 'approve', ); @@ -193,7 +195,6 @@ class Post { foreach ( $comments as $comment ) { // include self replies if ( $this->post->post_author === $comment->user_id ) { - //$comment_url = $comment->comment_ID; $comment_url = \add_query_arg( // array( 'p' => $this->post->ID, @@ -201,24 +202,23 @@ class Post { ), trailingslashit( site_url() ) ); - //\error_log( 'generate_replies: $comment' . print_r( $comment, true ) ); $items[] = $comment_url; + } else { + $ap_object = \unserialize(\get_comment_meta( $comment->comment_ID, 'ap_object', true )); + $comment_url = \get_comment_meta( $comment->comment_ID, 'source_url', true ); + if ( ! empty( $comment_url ) ){ + $items[] = \get_comment_meta( $comment->comment_ID, 'source_url', true ); + } + } } - //\error_log( 'generate_replies: $comments' . print_r( $comments, true ) ); + $replies = (object) array( 'type' => 'Collection', 'id' => \add_query_arg( array( 'replies' => '' ), $this->id ), 'first' => (object) array( 'type' => 'CollectionPage', 'partOf' => \add_query_arg( array( 'replies' => '' ), $this->id ), - 'next' => \add_query_arg( - array( - 'replies' => '', - 'page' => 1, - ), - $this->id - ), 'items' => $items, ), ); diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 61e4eb8..d83f85b 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -427,13 +427,6 @@ class Inbox { } } - //not all implementaions use url - if ( isset( $object['object']['id'] ) ) { - $source_url = \esc_url_raw( $object['object']['id'] ); - } else { - $source_url = \esc_url_raw( $object['object']['url'] ); - } - // if no name is set use peer username if ( ! empty( $meta['name'] ) ) { $name = \esc_attr( $meta['name'] ); @@ -446,10 +439,10 @@ class Inbox { } //Only create WP_Comment for public replies to local posts - if ( ( in_array( AS_PUBLIC, $object['to'] ) - || in_array( AS_PUBLIC, $object['cc'] ) ) + if ( ( 'public' === $audience ) + || ( 'unlisted' === $audience ) && ( ! empty( $comment_post_ID ) - || ! empty( $comment_parent ) + || ! empty( $comment_parent_ID ) ) ) { $commentdata = array( @@ -462,7 +455,7 @@ class Inbox { 'comment_parent' => $comment_parent_ID, 'comment_meta' => array( 'ap_object' => \serialize( $object ), - 'source_url' => $source_url, + 'source_url' => \esc_url_raw( $object['object']['id'] ), 'avatar_url' => $avatar_url, 'protocol' => 'activitypub', ), diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index 09af42a..d6a11a1 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -126,6 +126,7 @@ class Webfinger { * WebFinger Lookup to find user uri * * @param string $resource the WebFinger resource + * @return array ['href', 'name']. ex: href=https://domain.tld/user/webfinger, name=webfinger@domain.tld */ public static function webfinger_lookup( $webfinger ) { $activity_profile = null; diff --git a/templates/comment-json.php b/templates/comment-json.php index 13ba9cd..1749620 100644 --- a/templates/comment-json.php +++ b/templates/comment-json.php @@ -1,5 +1,5 @@ \Activitypub\get_context() ), $activitypub_comment->to_array() ); diff --git a/templates/replies-json.php b/templates/replies-json.php new file mode 100644 index 0000000..6135ad8 --- /dev/null +++ b/templates/replies-json.php @@ -0,0 +1,84 @@ + 'approve', + 'number' => '10', + 'offset' => $page, + 'type' => 'activitypub', + 'post_id' => $post->ID, + 'order' => 'ASC', +); +$comments = get_comments( $args ); + +$replies_request = \add_query_arg( $_SERVER['QUERY_STRING'], '', $post->guid ); +$collection_id = \remove_query_arg(['page', 'ap_comment_id'], $replies_request ); + +$json = new \stdClass(); +$json->{'@context'} = 'https://www.w3.org/ns/activitystreams'; +$json->id = $collection_id; + +$collectionPage = new \stdClass(); +$collectionPage->type = 'CollectionPage'; +$collectionPage->partOf = $collection_id; +$collectionPage->totalItems = \count( $comments ); // phpcs:ignore + +if ( $page && ( ( \ceil ( $collectionPage->totalItems / 10 ) ) > $page ) ) { // phpcs:ignore + $collectionPage->first = \add_query_arg( 'page', 1, $collectionPage->partOf ); // phpcs:ignore TODO + $collectionPage->next = \add_query_arg( 'page', $page + 1, $collectionPage->partOf ); // phpcs:ignore + $collectionPage->last = \add_query_arg( 'page', \ceil ( $collectionPage->totalItems / 10 ), $collectionPage->partOf ); // phpcs:ignore +} + +foreach ( $comments as $comment ) { + $remote_url = \get_comment_meta( $comment->comment_ID, 'source_url', true ); + if ( $remote_url ) { // + $collectionPage->items[] = $remote_url; + } else { + $activitypub_comment = new \Activitypub\Model\Comment( $comment ); + $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_NONE ); + $activitypub_activity->from_post( $activitypub_comment->to_array() ); + $collectionPage->items[] = $activitypub_activity->to_array(); // phpcs:ignore + } +} + +if ( ! \get_query_var( 'collection_page' ) ) { + $json->type = 'Collection'; + //if +10, embed first + $json->first = $collectionPage; +} else { + $json = $collectionPage; + +} + +// filter output +$json = \apply_filters( 'activitypub_json_replies_array', $json ); + +/* + * Action triggerd prior to the ActivityPub profile being created and sent to the client + */ +\do_action( 'activitypub_json_replies_pre' ); + +$options = 0; +// JSON_PRETTY_PRINT added in PHP 5.4 +if ( \get_query_var( 'pretty' ) ) { + $options |= \JSON_PRETTY_PRINT; // phpcs:ignore +} + +$options |= \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT; + +/* + * Options to be passed to json_encode() + * + * @param int $options The current options flags + */ +$options = \apply_filters( 'activitypub_json_replies_options', $options ); + +\header( 'Content-Type: application/activity+json' ); +echo \wp_json_encode( $json, $options ); + +/* + * Action triggerd after the ActivityPub profile has been created and sent to the client + */ +\do_action( 'activitypub_json_replies_comment' );