From 6ab14f1b0782dd4090249ff7072bf6cc6186e1d5 Mon Sep 17 00:00:00 2001
From: Django Doucet '; print_r($mentions); echo '
';
+ }
+
+ /**
+ * Adds ActivityPub Metabox to supported post types
+ */
+ public static function add_audience_metabox() {
+ $ap_post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) : array();
+ $ap_post_types[] = 'activitypub_mentions';
+ //$ap_post_types[] = 'comment';//TODO
+ foreach ($ap_post_types as $post_types) {
+ add_meta_box(
+ 'activitypub_post_audience',// Unique ID
+ __( 'Audience', 'activitypub' ), // Box title
+ [self::class, 'post_audience_html'],// Content callback, must be of type callable
+ $post_types,// Post types, Comments
+ 'side',
+ 'high',
+ );
+ }
+ }
+
+ /**
+ * Save Audience and Mentions Meta Fields
+ */
+ public static function save_post_audience($post_id) {
+ //wp_verify_nonce('ap_audience_meta');
+ if (array_key_exists('_ap_audience', $_POST)) {
+ \error_log('array_key_exists-_ap_audience: ' . $_POST['_ap_audience']);
+ update_post_meta(
+ $post_id,
+ '_ap_audience',
+ $_POST['_ap_audience']
+ );
+ }
+ if (array_key_exists('_ap_mentions', $_POST)) {
+ \error_log('array_key_exists-_ap_mentions: ' . $_POST['_ap_mentions']);
+ update_post_meta(
+ $post_id,
+ '_ap_mentions',
+ $_POST['_ap_mentions']
+ );
+ }
+ // if (array_key_exists('_ap_replyto', $_POST)) {
+ // \error_log('array_key_exists-_ap_replyto: ' . $_POST['_ap_replyto']);
+ // update_post_meta(
+ // $post_id,
+ // '_ap_replyto',
+ // $_POST['_ap_replyto']
+ // );
+ // }
+ // if (array_key_exists('parent_id', $_POST)) {
+ // \error_log('array_key_exists-_ap_replyto: ' . $_POST['parent_id']);
+ // remove_action( 'save_post_activitypub', 'save_post_audience' );
+
+ // // update the post, which calls save_post again
+ // wp_update_post(
+ // array( 'ID' => $post_id,
+ // 'post_parent' => $_POST['parent_id']
+ // )
+ // );
+
+ // // re-hook this function
+ // add_action( 'save_post_activitypub', 'save_post_audience' );
+ // }
+ }
+
+ /**
+ * Saves post as child of parent for reply graph
+ */
+ public static function save_post_parent($data, $postarr){
+ if ( isset( $postarr["post_parent"] ) ) {
+ $data["post_parent"] = $postarr["post_parent"];
+ }
+ // if ( $postarr["post_status"] === 'publish' && $postarr["_ap_audience"] === 'private_message' ) {
+ // $data["post_status"] = $postarr["_ap_audience"];
+ // }
+ // error_log( 'C2S:save_post_parent:data: ' . print_r($data,true));
+ // error_log( 'C2S:save_post_parent:postarr: ' . print_r($postarr,true));
+ return $data;
+ }
+
+ /**
+ * Audience fields
+ *
+ */
+ public static function post_audience_html($post)
+ {
+ wp_nonce_field( 'ap_audience_meta', 'ap_audience_meta_nonce' );
+ //$audience = $mentions = null;
+ if ( isset ( $post->ID ) ) {
+ $audience = get_post_meta($post->ID, '_ap_audience', true);
+ $mentions = get_post_meta($post->ID, '_ap_mentions', true);
+ //$replyto = get_post_meta($post->ID, '_ap_replyto', true);
+ if ( isset( $post->post_parent ) ){
+ $replyto = $post_parent = $post->post_parent;
+ }
+ }
+ if (array_key_exists('_ap_audience', $_REQUEST)) {
+ $audience = $_REQUEST['_ap_audience'];
+ }
+ if (array_key_exists('_ap_mentions', $_REQUEST)) {
+ \error_log('array_key_exists-_ap_mentions: ' . $_REQUEST['_ap_mentions']);
+ $mentions = $_REQUEST['_ap_mentions'];
+ }
+ // if (array_key_exists('_ap_replyto', $_REQUEST)) {
+ // \error_log('array_key_exists-_ap_replyto: ' . $_REQUEST['_ap_replyto']);
+ // $replyto = $_REQUEST['_ap_replyto'];
+ // $replyto = get_post_meta( $post_parent, '_source_url', true);
+ // }
+ if (array_key_exists('post_parent', $_REQUEST)) {
+ \error_log('array_key_exists-parent_id: ' . $_REQUEST['post_parent']);
+ $post_parent = $replyto = $_REQUEST['post_parent'];
+ }
+
+ ?>
+
+
+
'; print_r( $wp_post_types ); echo ''; + } + +} diff --git a/includes/class-signature.php b/includes/class-signature.php index 3dffb109..ffd5003d 100644 --- a/includes/class-signature.php +++ b/includes/class-signature.php @@ -53,7 +53,7 @@ public static function generate_key_pair( $user_id ) { $config = array( 'digest_alg' => 'sha512', 'private_key_bits' => 2048, - 'private_key_type' => \OPENSSL_KEYTYPE_RSA, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, ); $key = \openssl_pkey_new( $config ); @@ -91,7 +91,7 @@ public static function generate_signature( $user_id, $url, $date ) { $signed_string = "(request-target): post $path\nhost: $host\ndate: $date"; $signature = null; - \openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 ); + \openssl_sign( $signed_string, $signature, $key, OPENSSL_ALGO_SHA256 ); $signature = \base64_encode( $signature ); // phpcs:ignore $key_id = \get_author_posts_url( $user_id ) . '#main-key'; diff --git a/includes/functions.php b/includes/functions.php index 90dec8a7..52f5e58c 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -40,7 +40,7 @@ function safe_remote_post( $url, $body, $user_id ) { ), 'body' => $body, ); - +//\error_log( 'signature:' . print_r( $args, true ) ); $response = \wp_safe_remote_post( $url, $args ); \do_action( 'activitypub_safe_remote_post_response', $response, $url, $body, $user_id ); @@ -48,6 +48,33 @@ function safe_remote_post( $url, $body, $user_id ) { return $response; } +function forward_remote_post( $url, $body, $user_id ) { + $date = \gmdate( 'D, d M Y H:i:s T' ); + //$signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date ); + + $wp_version = \get_bloginfo( 'version' ); + $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) ); + $args = array( + 'timeout' => 100, + 'limit_response_size' => 1048576, + 'redirection' => 3, + 'user-agent' => "$user_agent; ActivityPub", + 'headers' => array( + 'Accept' => 'application/activity+json', + 'Content-Type' => 'application/activity+json', + // 'Signature' => $signature, + 'Date' => $date, + ), + 'body' => $body, + ); + + $response = \wp_safe_remote_post( $url, $args ); + + \do_action( 'activitypub_forward_remote_post_response', $response, $url, $body, $user_id ); + + return $response; +} + function safe_remote_get( $url, $user_id ) { $date = \gmdate( 'D, d M Y H:i:s T' ); $signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date ); @@ -89,7 +116,7 @@ function get_webfinger_resource( $user_id ) { $user = \get_user_by( 'id', $user_id ); - return $user->user_login . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST ); + return $user->user_login . '@' . \wp_parse_url( \home_url(), PHP_URL_HOST ); } /** @@ -157,7 +184,7 @@ function get_inbox_by_actor( $actor ) { return $metadata['inbox']; } - return new \WP_Error( 'activitypub_no_inbox', \__( 'No "Inbox" found', 'activitypub' ), $metadata ); + return new \WP_Error( 'activitypub_no_inbox', __( 'No "Inbox" found', 'activitypub' ), $metadata ); } /** @@ -188,6 +215,7 @@ function get_publickey_by_actor( $actor, $key_id ) { function get_follower_inboxes( $user_id ) { $followers = \Activitypub\Peer\Followers::get_followers( $user_id ); + $inboxes = array(); foreach ( $followers as $follower ) { @@ -201,7 +229,6 @@ function get_follower_inboxes( $user_id ) { } $inboxes[ $inbox ][] = $follower; } - return $inboxes; } @@ -253,13 +280,13 @@ function url_to_authorid( $url ) { global $wp_rewrite; // check if url hase the same host - if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) !== \wp_parse_url( $url, \PHP_URL_HOST ) ) { + if ( wp_parse_url( site_url(), PHP_URL_HOST ) !== wp_parse_url( $url, PHP_URL_HOST ) ) { return 0; } // first, check to see if there is a 'author=N' to match against if ( \preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) { - $id = \absint( $values[1] ); + $id = absint( $values[1] ); if ( $id ) { return $id; } @@ -279,7 +306,7 @@ function url_to_authorid( $url ) { // match the rewrite rule with the passed url if ( \preg_match( '/https?:\/\/(.+)' . \preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) { - $user = \get_user_by( 'slug', $match[2] ); + $user = get_user_by( 'slug', $match[2] ); if ( $user ) { return $user->ID; } @@ -288,6 +315,89 @@ function url_to_authorid( $url ) { return 0; } +/** + * Verify if url is a local comment, + * Or if it is a previously received remote comment + * + * return int comment_id + */ +function url_to_commentid( $comment_url ) { + $post_url = \url_to_postid( $comment_url ); + + if ( $post_url ) { + //for local comment parent + $comment_id = explode( '#comment-', $comment_url ); + if ( isset( $comment_id[1] ) ){ + return $comment_id[1]; + } else { + return null; + } + + } else { + //remote comment parent, assuming the parent was also recieved + //Compare inReplyTo with source_url from meta, to determine if local comment_id exists for peer replied object + $comment_args = array( + 'type' => 'activitypub', + 'meta_query' => array( + array( + 'key' => 'source_url', + 'value' => $comment_url, + ) + ) + ); + $comments_query = new \WP_Comment_Query; + $comments = $comments_query->query( $comment_args ); + $found_comment_ids = array(); + if ( $comments ) { + foreach ( $comments as $comment ) { + $found_comment_ids[] = $comment->comment_ID; + } + return $found_comment_ids[0]; + } + return null; + } +} + +function add_recipients( $recipient, $self ) { + $cc = array( 'https://www.w3.org/ns/activitystreams#Public' ); + $cc[] = $recipient; + $cc[] = $self . 'followers'; + return $cc; +} + +function tag_user( $recipient ) { + $tagged_user = array( + 'type' => 'Mention', + 'href' => $recipient, + 'name' => \Activitypub\url_to_webfinger( $recipient ), + ); + $tag[] = $tagged_user; + return $tag; +} + +function url_to_webfinger( $user_url ) { + $user_url = \untrailingslashit( $user_url ); + $user_url_array = explode( '/', $user_url ); + $user_name = end( $user_url_array ); + $url_host = parse_url( $user_url , PHP_URL_HOST ); + $webfinger = '@' . $user_name . '@' . $url_host; + return $webfinger; +} + +/** + * 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] . '?' . $comment_id[1]; + return $comment_id; +} + /** * Get the blacklist from the WordPress options table * @@ -296,8 +406,8 @@ function url_to_authorid( $url ) { * @uses apply_filters() Calls 'activitypub_blacklist' filter */ function get_blacklist() { - $blacklist = \get_option( 'activitypub_blacklist', 'gab.com' ); - $blacklist_hosts = \explode( \PHP_EOL, $blacklist ); + $blacklist = \get_option( 'activitypub_blacklist' ); + $blacklist_hosts = \explode( PHP_EOL, $blacklist ); // if no values have been set, revert to the defaults if ( ! $blacklist || ! $blacklist_hosts || ! \is_array( $blacklist_hosts ) ) { @@ -325,10 +435,38 @@ function get_blacklist() { */ function is_blacklisted( $url ) { foreach ( \ActivityPub\get_blacklist() as $blacklisted_host ) { - if ( \stripos( $url, $blacklisted_host ) !== false ) { + if ( \strpos( $url, $blacklisted_host ) !== false ) { return true; } } return false; } + +/** + * in_audience + * return true if wp_user is in an audience array + */ +// function in_audience( $needles, $haystack ) { +// foreach ($needles as $needle) { +// if ( \strpos( $haystack, $needle ) !== false ) { +// return true; +// } +// } +// } + +// function in_audience( $needles, $haystack ) { +// foreach ($needles as $needle) { +// if ( \strpos( $haystack, $needle ) !== false ) { +// return true; +// } +// } +// } + +/* polyfill against php 8 */ +// https://php.watch/versions/8.0/str_contains +if ( !function_exists( 'str_contains' ) ) { + function str_contains( $haystack, $needle ) { + return '' === $needle || false !== \strpos( $haystack, $needle ); + } +} diff --git a/includes/model/class-activity.php b/includes/model/class-activity.php index 4aa41077..dae3740a 100644 --- a/includes/model/class-activity.php +++ b/includes/model/class-activity.php @@ -41,7 +41,9 @@ public function __call( $method, $params ) { } if ( \strncasecmp( $method, 'set', 3 ) === 0 ) { + //\error_log('set_params: ' . print_r( $params, true ) ); $this->$var = $params[0]; + //$this->$var = [ 'https://www.w3.org/ns/activitystreams#Public', $params[0][0]]; } } @@ -50,10 +52,17 @@ public function from_post( $object ) { $this->published = $object['published']; $this->actor = $object['attributedTo']; $this->id = $object['id']; + $this->cc = array( 'https://www.w3.org/ns/activitystreams#Public', $object['attributedTo'] . 'followers' ); } public function from_comment( $object ) { - + \error_log( 'from_comment' ); + $this->object = $object; + $this->published = $object['published']; + $this->actor = $object['attributedTo']; + $this->id = $object['id'] . '-activity'; + $this->cc = $object['cc']; + $this->tag = $object['tag']; } public function to_array() { @@ -69,10 +78,11 @@ public function to_array() { } public function to_json() { - return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT ); + return \wp_json_encode( $this->to_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT ); } public function to_simple_array() { + \error_log( 'to_simple_array' ); $activity = array( '@context' => $this->context, 'type' => $this->type, @@ -90,6 +100,6 @@ public function to_simple_array() { } public function to_simple_json() { - return \wp_json_encode( $this->to_simple_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT ); + return \wp_json_encode( $this->to_simple_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT ); } } diff --git a/includes/model/class-comment.php b/includes/model/class-comment.php new file mode 100644 index 00000000..a5b3f7a9 --- /dev/null +++ b/includes/model/class-comment.php @@ -0,0 +1,95 @@ +comment = \get_comment( $comment ); + } + + public function get_comment() { + return $this->comment; + } + + public function get_comment_author() { + return $this->comment->comment_author; + } + + public function to_array() { + $comment = $this->comment; + error_log( 'to_array()' ); + // error_log( print_r($comment, true) ); + $ap_object = \get_comment_meta( $comment->comment_ID, 'ap_object', true ); + // if($ap_object){ + // \error_log('is_ap_object:'); + // \error_log( print_r( $ap_object, true ) ); + // } + $parent_ap_object = \get_comment_meta( $comment->comment_parent, 'ap_object', true ); + if($parent_ap_object){ + // \error_log('parent_ap_object:'); + // \error_log( print_r( \unserialize($parent_ap_object), true ) ); + $parent_ap_object = \unserialize($parent_ap_object); + + } + $cc_recipients = $mentions = null; + $self = $comment->comment_author_url; + $parent_comment = \get_comment( $comment->comment_parent ); + if ( $parent_comment ) { + $recipient = $parent_comment->comment_author_url; + $cc_recipients = \Activitypub\add_recipients( $recipient, $self ); + $mentions = \Activitypub\tag_user( $recipient ); + $inReplyTo = \get_comment_meta( $comment->comment_parent, 'source_url', true ); + } else { + $inReplyTo = $comment->comment_parent; + } + + //ID must be unique https://www.w3.org/TR/activitypub/#obj-id + $comment_id = \Activitypub\normalize_comment_url( $comment ); + + // error_log( '$cc_recipients: ' . print_r( $cc_recipients, true ) ); + // error_log( '$mentions: ' . print_r( $mentions, true ) ); + //comment_id $source_url = get_comment_meta( $comment->comment_ID, 'source_url', true ); + + //error_log('$inReplyTo: ' . $inReplyTo ); + //error_log( print_r($inReplyTo, true) ); + if( empty( $ap_object ) ) { + $array = array( + 'id' => $comment_id, //\get_comment_link( $comment ), + 'type' => 'Note', + 'published' => \date( 'Y-m-d\TH:i:s\Z', \strtotime( $comment->comment_date ) ), + 'attributedTo' => \esc_url_raw( $comment->comment_author_url ), + 'summary' => '',//$this->get_the_title(), + 'inReplyTo' => \esc_url_raw( $inReplyTo ), + 'content' => $comment->comment_content, + 'contentMap' => array( + \strstr( \get_locale(), '_', true ) => $comment->comment_content, + ), + 'source' => \get_comment_link( $comment ), + 'url' => \get_comment_link( $comment ), + 'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ), + //'to' => array( 'https://www.w3.org/ns/activitystreams#Public', $self ), + 'cc' => $cc_recipients, + 'tag' => $mentions, + ); + } else { + $array = unserialize( $ap_object ); + } + + //error_log( 'to_array' ); + return \apply_filters( 'activitypub_post', $array ); + } + + public function to_json() { + return \wp_json_encode( $this->to_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT ); + } + +} \ No newline at end of file diff --git a/includes/model/class-post.php b/includes/model/class-post.php index ed7e9c0a..0d427665 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -8,13 +8,6 @@ */ class Post { private $post; - private $post_author; - private $permalink; - private $summary; - private $content; - private $attachments; - private $tags; - private $object_type; /** * Initialize the class, registering WordPress hooks @@ -26,64 +19,54 @@ public static function init() { public function __construct( $post = null ) { $this->post = \get_post( $post ); - - $this->post_author = $this->post->post_author; - $this->permalink = $this->generate_permalink(); - $this->summary = $this->generate_the_title(); - $this->content = $this->generate_the_content(); - $this->attachments = $this->generate_attachments(); - $this->tags = $this->generate_tags(); - $this->object_type = $this->generate_object_type(); } - public function __call( $method, $params ) { - $var = \strtolower( \substr( $method, 4 ) ); - - if ( \strncasecmp( $method, 'get', 3 ) === 0 ) { - return $this->$var; - } + public function get_post() { + return $this->post; + } - if ( \strncasecmp( $method, 'set', 3 ) === 0 ) { - $this->$var = $params[0]; - } + public function get_post_author() { + return $this->post->post_author; } public function to_array() { $post = $this->post; $array = array( - 'id' => $this->permalink, - 'type' => $this->object_type, + 'id' => \get_permalink( $post ), + 'type' => $this->get_object_type(), 'published' => \date( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_date ) ), 'attributedTo' => \get_author_posts_url( $post->post_author ), - 'summary' => $this->summary, + 'summary' => $this->get_the_title(), 'inReplyTo' => null, - 'content' => $this->content, + 'content' => $this->get_the_content(), 'contentMap' => array( - \strstr( \get_locale(), '_', true ) => $this->content, + \strstr( \get_locale(), '_', true ) => $this->get_the_content(), ), 'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ), 'cc' => array( 'https://www.w3.org/ns/activitystreams#Public' ), - 'attachment' => $this->attachments, - 'tag' => $this->tags, + 'attachment' => $this->get_attachments(), + 'tag' => $this->get_tags(), + 'replies' => array( //$this->get_the_comments(); 'activitypub/1.0/post/$post->ID/replies' + 'id' => \get_rest_url(null, 'activitypub/1.0/post/') . $post->ID . '/replies', + 'type' => 'Collection', + 'first' => array( + 'type' => 'CollectionPage', + 'next' => \get_rest_url(null, 'activitypub/1.0/post/') . $post->ID . '/replies', + 'partOf' => \get_rest_url(null, 'activitypub/1.0/post/') . $post->ID . '/replies', + 'items' => [], + ), + ), ); return \apply_filters( 'activitypub_post', $array ); } public function to_json() { - return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT ); - } - - public function generate_permalink() { - $post = $this->post; - $permalink = \get_permalink( $post ); - - // replace 'trashed' for delete activity - return \str_replace( '__trashed', '', $permalink ); + return \wp_json_encode( $this->to_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT ); } - public function generate_attachments() { + public function get_attachments() { $max_images = \apply_filters( 'activitypub_max_images', 3 ); $images = array(); @@ -131,7 +114,7 @@ public function generate_attachments() { $image = array( 'type' => 'Image', 'url' => $thumbnail[0], - 'mediaType' => $mimetype, + 'mediaType' => $mimetype ); if ( $alt ) { $image['name'] = $alt; @@ -143,7 +126,7 @@ public function generate_attachments() { return $images; } - public function generate_tags() { + public function get_tags() { $tags = array(); $post_tags = \get_the_tags( $this->post->ID ); @@ -169,7 +152,7 @@ public function generate_tags() { * * @return string the object-type */ - public function generate_object_type() { + public function get_object_type() { if ( 'wordpress-post-format' !== \get_option( 'activitypub_object_type', 'note' ) ) { return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) ); } @@ -226,23 +209,23 @@ public function generate_object_type() { return $object_type; } - public function generate_the_content() { + public function get_the_content() { if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return $this->generate_the_post_summary(); + return $this->get_the_post_summary(); } if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) { - return $this->generate_the_title(); + return $this->get_the_title(); } - return $this->generate_the_post_content(); + return $this->get_the_post_content(); } - public function generate_the_title() { - if ( 'Article' === $this->generate_object_type() ) { - $title = \generate_the_title( $this->post ); + public function get_the_title() { + if ( 'Article' === $this->get_object_type() ) { + $title = \get_the_title( $this->post ); - return \html_entity_decode( $title, \ENT_QUOTES, 'UTF-8' ); + return \html_entity_decode( $title, ENT_QUOTES, 'UTF-8' ); } return null; @@ -255,7 +238,7 @@ public function generate_the_title() { * * @return string The excerpt. */ - public function generate_the_post_excerpt( $excerpt_length = 400 ) { + public function get_the_post_excerpt( $excerpt_length = 400 ) { $post = $this->post; $excerpt = \get_post_field( 'post_excerpt', $post ); @@ -290,7 +273,7 @@ public function generate_the_post_excerpt( $excerpt_length = 400 ) { * * @return string The content. */ - public function generate_the_post_content() { + public function get_the_post_content() { $post = $this->post; $content = \get_post_field( 'post_content', $post ); @@ -298,9 +281,9 @@ public function generate_the_post_content() { $filtered_content = \apply_filters( 'the_content', $content ); $filtered_content = \apply_filters( 'activitypub_the_content', $filtered_content, $this->post ); - $decoded_content = \html_entity_decode( $filtered_content, \ENT_QUOTES, 'UTF-8' ); + $decoded_content = \html_entity_decode( $filtered_content, ENT_QUOTES, 'UTF-8' ); - $allowed_html = \apply_filters( 'activitypub_allowed_html', '
' );
+ $allowed_html = \apply_filters( 'activitypub_allowed_html', '' );
return \trim( \preg_replace( '/[\r\n]{2,}/', '', \strip_tags( $decoded_content, $allowed_html ) ) );
}
@@ -312,13 +295,13 @@ public function generate_the_post_content() {
*
* @return string The excerpt.
*/
- public function generate_the_post_summary( $summary_length = 400 ) {
- $summary = $this->generate_the_post_excerpt( $summary_length );
+ public function get_the_post_summary( $summary_length = 400 ) {
+ $summary = $this->get_the_post_excerpt( $summary_length );
$filtered_summary = \apply_filters( 'the_excerpt', $summary );
$filtered_summary = \apply_filters( 'activitypub_the_summary', $filtered_summary, $this->post );
- $decoded_summary = \html_entity_decode( $filtered_summary, \ENT_QUOTES, 'UTF-8' );
+ $decoded_summary = \html_entity_decode( $filtered_summary, ENT_QUOTES, 'UTF-8' );
$allowed_html = \apply_filters( 'activitypub_allowed_html', '' );
diff --git a/includes/peer/class-followers.php b/includes/peer/class-followers.php
index c1846a48..bc5af7b2 100644
--- a/includes/peer/class-followers.php
+++ b/includes/peer/class-followers.php
@@ -10,7 +10,6 @@ class Followers {
public static function get_followers( $author_id ) {
$followers = \get_user_option( 'activitypub_followers', $author_id );
-
if ( ! $followers ) {
return array();
}
@@ -21,7 +20,7 @@ public static function get_followers( $author_id ) {
isset( $follower['type'] ) &&
'Person' === $follower['type'] &&
isset( $follower['id'] ) &&
- false !== \filter_var( $follower['id'], \FILTER_VALIDATE_URL )
+ false !== \filter_var( $follower['id'], FILTER_VALIDATE_URL )
) {
$followers[ $key ] = $follower['id'];
}
@@ -45,7 +44,7 @@ public static function add_follower( $actor, $author_id ) {
isset( $actor['type'] ) &&
'Person' === $actor['type'] &&
isset( $actor['id'] ) &&
- false !== \filter_var( $actor['id'], \FILTER_VALIDATE_URL )
+ false !== \filter_var( $actor['id'], FILTER_VALIDATE_URL )
) {
$actor = $actor['id'];
}
@@ -68,8 +67,12 @@ public static function add_follower( $actor, $author_id ) {
public static function remove_follower( $actor, $author_id ) {
$followers = \get_user_option( 'activitypub_followers', $author_id );
-
+ \error_log('author_id: ' . print_r( $author_id, true ) );
+ \error_log('actor: ' . print_r( $actor, true ) );
+ \error_log('followers_array: ' . print_r( $followers, true ) );
foreach ( $followers as $key => $value ) {
+ \error_log('value: ' . print_r( $value, true ) );
+ \error_log('key: ' . print_r( $key, true ) );
if ( $value === $actor ) {
unset( $followers[ $key ] );
}
diff --git a/includes/peer/class-mentions.php b/includes/peer/class-mentions.php
new file mode 100644
index 00000000..a0056c89
--- /dev/null
+++ b/includes/peer/class-mentions.php
@@ -0,0 +1,175 @@
+ 'activitypub_mentions',//TODO: rename to activitypub
+ 'post_status' => array( 'followers_only', 'private', 'private_message', 'unlisted', 'public' ),
+ 'numberposts' => -1
+ );
+ $mentions = get_posts( $args );
+
+ // if ( ! $mentions ) {
+ // return array();
+ // }
+
+ $current_user_id = get_current_user_id();
+ $personal_mentions = array();
+ if ( $debug ) {
+ echo "get_mentions()
"; print_r($mentions); echo "
";
+ }
+
+ foreach ( $mentions as $mention ) :
+ //$local_user = get_post_meta( $mention->ID, 'local_user', true );
+ $local_user = $mention->post_author;
+ $target_user = get_post_meta( $mention->ID, 'target_user', true );
+ if ( $current_user_id == $local_user ) {
+ $personal_mentions[] = $mention;
+ }
+ endforeach;
+ return $personal_mentions;
+ }
+
+ public static function get_views( ) {
+ $views = array();
+ $current = ( !empty($_REQUEST['mention_type']) ? $_REQUEST['mention_type'] : 'all');
+
+ $class = ($current == 'all' ? ' class="current"' :'');
+ $all_url = remove_query_arg('mention_type');
+ $views['all'] = "All";
+
+ $private_message_url = add_query_arg('mention_type','private_message');
+ $class = ($current == 'private_message' ? ' class="current"' :'');
+ $views['private_message'] = "Private messages";
+
+ $followers_only_url = add_query_arg('mention_type','followers_only');
+ $class = ($current == 'followers_only' ? ' class="current"' :'');
+ $views['followers_only'] = "Followers only";
+
+ $unlisted_url = add_query_arg('mention_type','Unlisted');
+ $class = ($current == 'activitypub_ul' ? ' class="current"' :'');
+ $views['activitypub_ul'] = "Unlisted";
+
+ $public_url = add_query_arg('mention_type','Public');
+ $class = ($current == 'activitypub_public' ? ' class="current"' :'');
+ $views['activitypub_public'] = "Public";
+
+ return $views;
+ }
+
+ public static function count_mentions( ) {
+ $debug = 'debug';
+ $mentions = self::get_mentions( $debug );
+ return \count( $mentions );
+ }
+
+ public static function test_reply_comment_func($str, $input) {
+ error_log('test_reply_comment_func');
+ extract($input);
+ $table_row = TRUE;
+ if ($mode == 'single') {
+ $wp_list_table = _get_list_table('WP_Post_Comments_List_Table');
+ } else {
+ $wp_list_table = _get_list_table('WP_Comments_List_Table');
+ }
+ // Get editor string
+ ob_start();
+ $quicktags_settings = array('buttons' => 'strong,em,link,block,del,ins,img,ul,ol,li,code,spell,close');
+ wp_editor('', 'replycontent', array('media_buttons' => false, 'tinymce' => false, 'quicktags' => $quicktags_settings, 'tabindex' => 104));
+ $editorStr = ob_get_contents();
+ ob_end_clean();
+ // Get nonce string
+ ob_start();
+ wp_nonce_field("replyto-comment", "_ajax_nonce-replyto-comment", false);
+ if (current_user_can("unfiltered_html"))
+ wp_nonce_field("unfiltered-html-comment", "_wp_unfiltered_html_comment", false);
+ $nonceStr = ob_get_contents();
+ ob_end_clean();
+ $content = '
n";
+ apply_filters( 'wp_comment_reply', $content, $args );
+ }
+
+
+}
diff --git a/includes/rest/class-followers.php b/includes/rest/class-followers.php
index 2d3d9ea7..94f406c8 100644
--- a/includes/rest/class-followers.php
+++ b/includes/rest/class-followers.php
@@ -26,6 +26,7 @@ public static function register_routes() {
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Followers', 'get' ),
'args' => self::request_parameters(),
+ 'permission_callback' => '__return_true',
),
)
);
diff --git a/includes/rest/class-following.php b/includes/rest/class-following.php
index 4a39fc53..3e2a8aa0 100644
--- a/includes/rest/class-following.php
+++ b/includes/rest/class-following.php
@@ -26,6 +26,7 @@ public static function register_routes() {
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Following', 'get' ),
'args' => self::request_parameters(),
+ 'permission_callback' => '__return_true',
),
)
);
diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php
index 7aa0effb..e0d0680b 100644
--- a/includes/rest/class-inbox.php
+++ b/includes/rest/class-inbox.php
@@ -20,6 +20,16 @@ public static function init() {
//\add_action( 'activitypub_inbox_like', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
//\add_action( 'activitypub_inbox_announce', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
\add_action( 'activitypub_inbox_create', array( '\Activitypub\Rest\Inbox', 'handle_create' ), 10, 2 );
+
+ //Move to c2s or other place
+ \add_filter( 'preprocess_comment' , array( '\Activitypub\Rest\Inbox', 'preprocess_comment_handler' ) );
+
+ //move to c2s
+ \add_filter( 'comment_post' , array( '\Activitypub\Rest\Inbox', 'postprocess_comment_handler' ), 10, 3 );
+ // \add_action( 'pre_get_posts', array( '\Activitypub\Rest\Inbox', 'filter_private_messages' ), 11 );
+ // \add_action( 'wp_count_comments', array( '\Activitypub\Rest\Inbox', 'count_comments' ), 11, 2 );
+ // \add_filter( 'comments_clauses', array( '\Activitypub\Rest\Inbox', 'ap_personal_comment_list'), 11 );
+ // \add_action( 'admin_head', array( '\Activitypub\Rest\Inbox', 'comments_styles' ), 21 );
}
/**
@@ -31,6 +41,7 @@ public static function register_routes() {
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox' ),
+ 'permission_callback' => '__return_true'
),
)
);
@@ -38,12 +49,24 @@ public static function register_routes() {
\register_rest_route(
'activitypub/1.0', '/users/(?P\d+)/inbox', array(
array(
- 'methods' => \WP_REST_Server::EDITABLE,
+ 'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox' ),
'args' => self::request_parameters(),
+ 'permission_callback' => '__return_true'
),
)
);
+
+ // \register_rest_route(
+ // 'activitypub/1.0', '/users/(?P\d+)/inbox', array(
+ // array(
+ // 'methods' => \WP_REST_Server::READABLE,
+ // 'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_get' ),
+ // 'args' => self::get_parameters(),
+ // 'permission_callback' => '__return_true'
+ // ),
+ // )
+ // );
}
/**
@@ -85,15 +108,39 @@ public static function serve_request( $served, $result, $request, $server ) {
public static function user_inbox( $request ) {
$user_id = $request->get_param( 'user_id' );
+// error_log( 'user_inbox $request: ' . print_r( $request, true ) );
$data = $request->get_params();
$type = $request->get_param( 'type' );
\do_action( 'activitypub_inbox', $data, $user_id, $type );
- \do_action( "activitypub_inbox_{$type}", $data, $user_id );
+// \do_action( "activitypub_inbox_{$type}", $data, $user_id );
return new \WP_REST_Response( array(), 202 );
}
+ /**
+ * Renders the user-inbox
+ *
+ * @param WP_REST_Request $request
+ *
+ * @return WP_REST_Response
+ */
+ public static function user_inbox_get( $request ) {
+ $user_id = $request->get_param( 'user_id' );
+
+ $json = new \stdClass();
+
+ $json->{'@context'} = \Activitypub\get_context();
+ $json->id = \get_rest_url( null, "/activitypub/1.0/users/$user_id/inbox" ); // phpcs:ignore
+ $json->type = 'OrderedCollection';
+
+ $response = new \WP_REST_Response( $json, 200 );
+
+ $response->header( 'Content-Type', 'application/activity+json' );
+
+ return $response;
+ }
+
/**
* The shared inbox
*
@@ -168,6 +215,55 @@ public static function request_parameters() {
return $params;
}
+ /**
+ * The supported parameters
+ *
+ * @return array list of parameters
+ */
+ public static function get_parameters() {
+ $params = array();
+
+ $params['page'] = array(
+ 'type' => 'integer',
+ );
+
+ $params['user_id'] = array(
+ 'required' => true,
+ 'type' => 'integer',
+ );
+
+ $params['id'] = array(
+// 'required' => true,
+ 'type' => 'string',
+ 'validate_callback' => function( $param, $request, $key ) {
+ if ( ! \is_string( $param ) ) {
+ $param = $param['id'];
+ }
+ return ! \Activitypub\is_blacklisted( $param );
+ },
+ 'sanitize_callback' => 'esc_url_raw',
+ );
+
+ $params['actor'] = array(
+// 'required' => true,
+ //'type' => array( 'object', 'string' ),
+ 'validate_callback' => function( $param, $request, $key ) {
+ if ( ! \is_string( $param ) ) {
+ $param = $param['id'];
+ }
+ return ! \Activitypub\is_blacklisted( $param );
+ },
+ 'sanitize_callback' => function( $param, $request, $key ) {
+ if ( ! \is_string( $param ) ) {
+ $param = $param['id'];
+ }
+ return \esc_url_raw( $param );
+ },
+ );
+
+ return $params;
+ }
+
/**
* Handles "Follow" requests
*
@@ -189,7 +285,6 @@ public static function handle_follow( $object, $user_id ) {
$activity->set_id( \get_author_posts_url( $user_id ) . '#follow' . \preg_replace( '~^https?://~', '', $object['actor'] ) );
$activity = $activity->to_simple_json();
-
$response = \Activitypub\safe_remote_post( $inbox, $activity, $user_id );
}
@@ -221,7 +316,7 @@ public static function handle_reaction( $object, $user_id ) {
'comment_type' => \esc_attr( \strtolower( $object['type'] ) ),
'comment_parent' => 0,
'comment_meta' => array(
- 'source_url' => \esc_url_raw( $object['id'] ),
+ 'source_url' => \esc_url_raw( $object['attributedTo'] ),
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
'protocol' => 'activitypub',
),
@@ -244,28 +339,343 @@ public static function handle_reaction( $object, $user_id ) {
*/
public static function handle_create( $object, $user_id ) {
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
+ error_log( '$meta[actor]: ' . print_r( $meta, true ) );
+
+ $comment_post_ID = \url_to_postid( $object['object']['inReplyTo'] );
+ $comment_parent = $comment_parent_ID = 0;
+ //if not a direct reply to a post
+ if ( $comment_post_ID === 0 ) {
+ //verify if reply to a local or remote received comment
+ $comment_parent_ID = \Activitypub\url_to_commentid( \esc_url_raw( $object['object']['inReplyTo'] ) );
+ if ( !is_null( $comment_parent_ID ) ) {
+ //replied to a local comment (which has a post_ID)
+ $comment_parent = get_comment( $comment_parent_ID );
+ $comment_post_ID = $comment_parent->comment_post_ID;
+ }
+ }
- $commentdata = array(
- 'comment_post_ID' => \url_to_postid( $object['object']['inReplyTo'] ),
- 'comment_author' => \esc_attr( $meta['name'] ),
- 'comment_author_url' => \esc_url_raw( $object['actor'] ),
- 'comment_content' => \wp_filter_kses( $object['object']['content'] ),
- 'comment_type' => '',
- 'comment_author_email' => '',
- 'comment_parent' => 0,
- 'comment_meta' => array(
- 'source_url' => \esc_url_raw( $object['object']['url'] ),
- 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
- 'protocol' => 'activitypub',
- ),
- );
+ //not all implementaions use url
+ if ( isset( $object['object']['url'] ) ) {
+ $source_url = \esc_url_raw( $object['object']['url'] );
+ } else {
+ $source_url = \esc_url_raw( $object['object']['id'] );
+ }
- // disable flood control
- \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
+ error_log( 'if $meta[name]: ' . print_r( $meta['name'], true ) );
+ // if no name is set use the peer username
+ if ( !empty( $meta['name'] ) ) {
+ $name = \esc_attr( $meta['name'] );
+ error_log( '$meta[name]: ' . print_r( $meta['name'], true ) );
+ } else {
+ $name = \esc_attr( $meta['preferredUsername'] );
+ error_log( '$meta[preferredUsername]: ' . print_r( $meta['preferredUsername'], true ) );
+ }
+ error_log( 'inbox:handle_create:object: ' . print_r( $object, true ) );
+
+ //Only create comments for public replies to posts
+// ??? Only create comments for public replies to PUBLIC posts
+// ??? Why not private replies to posts (should we manage private comments? [the global comments system])
+ if ( ( in_array( 'https://www.w3.org/ns/activitystreams#Public', $object['to'] )
+ || in_array( 'https://www.w3.org/ns/activitystreams#Public', $object['cc'] ) )
+ && ( !empty( $comment_post_ID )
+ || !empty ( $comment_parent )
+ ) ) {
+
+ $commentdata = array(
+ 'comment_post_ID' => $comment_post_ID,
+ 'comment_author' => $name,
+ 'comment_author_url' => \esc_url_raw( $object['actor'] ),
+ 'comment_content' => \wp_filter_kses( $object['object']['content'] ),
+ 'comment_type' => 'activitypub',
+ 'comment_author_email' => '',
+ 'comment_parent' => $comment_parent_ID,
+ 'comment_meta' => array(
+ //'local_user' => $object['user_id'],//access get_post_field ('post_author', 'comment_post_ID');
+ 'inReplyTo' => \esc_url_raw( $object['object']['inReplyTo'] ),//needed? (if replying to someone else on thread, but not received)non-wp status - comment_post_ID, comment_parent
+ 'source_url' => $source_url,
+ 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
+ 'ap_object' => \serialize( $object ), //$object for inbox-forwarding
+ 'protocol' => 'activitypub',
+ ),
+ );
+
+ // disable flood control
+ \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
+
+ $state = \wp_new_comment( $commentdata, true );
+
+ // re-add flood control
+ \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
+
+ } else {
+ //Not a public reply to a public post
+// TODO if $object['attachment']... append to content
+ $title = $summary = null;
+ if ( isset( $object['object']['summary'] ) ) {
+ $title = \wp_trim_words( $object['object']['summary'], 10 );
+ $summary = \wp_strip_all_tags( $object['object']['summary'] );
+ }
+ $to = get_user_by( 'id', $object['user_id'] );
+ $to_url = get_author_posts_url( $object['user_id'] );
+ error_log( 'inbox:handle_create:to: ' . $to_url );
+ // TODO if empty( $object['object']['summary'] ) trim+filter $object['object']['content']
+ $postdata = array(
+ 'post_content' => \wp_filter_kses( $object['object']['content'] ),
+ 'post_title' => $title,
+ 'post_excerpt' => $summary,
+ 'post_status' => 'private_message',
+ 'post_type' => 'activitypub_mentions',//activitypub
+ //'post_hierarchy' => comment_parent_ID
+ 'post_parent' => \esc_url_raw( $object['object']['inReplyTo'] ),
+ 'meta_input' => array(
+ '_local_user' => $object['user_id'],
+ '_ap_object' => $object,
+ '_read_status' => 'unread',
+ //'mention_type' => \esc_html($object['object']['type']),
+ '_inreplyto' => \esc_url_raw( $object['object']['inReplyTo'] ),
+ '_author' => $name,
+ '_author_url' => \esc_url_raw( $object['actor'] ),
+ '_source_url' => $source_url,
+ '_avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
+ '_protocol' => 'activitypub',
+ ),
+ );
+
+ //NON-Public
+ if ( !in_array( 'https://www.w3.org/ns/activitystreams#Public', $object['to'] )
+ && !in_array( 'https://www.w3.org/ns/activitystreams#Public', $object['cc'] ) ) {
+
+ if ( in_array($object['object']['attributedTo'] . '/followers', $object['to']) || in_array($object['object']['attributedTo'] . '/followers', $object['cc']) ) {
+ //Followers Only
+ error_log( 'AP: Followers Only: ' . $to->user_login );
+ $postdata['post_status'] = 'followers_only';
+ $postdata['meta_input']['mention_type'] = 'activitypub_fo';
+ } elseif ( in_array( get_author_posts_url( $object['user_id'] ), $object['to']) || in_array(get_author_posts_url($to->ID), $object['cc']) ) {
+ //Private Message
+ error_log( 'AP: Private Message to: ' . $to->user_login );
+ $postdata['post_status'] = 'private_message';
+ $postdata['meta_input']['mention_type'] = 'activitypub_dm';
+ } else {
+ error_log( 'AP: WTF some type of non public mention to: ' . $to->user_login );
+ }
+ } elseif ( empty( \url_to_postid( $object['object']['inReplyTo'] ) ) ) {
+ // This should catch public mentions (non-reply)
+ // https://www.w3.org/ns/activitystreams#Public
+ if ( !in_array( 'https://www.w3.org/ns/activitystreams#Public', $object['to'] )
+ && in_array( 'https://www.w3.org/ns/activitystreams#Public', $object['cc'] ) ) {
+ error_log( 'AP: Unlisted mention : ' . $to->user_login );
+ $postdata['post_status'] = 'unlisted';//or public?
+ $postdata['meta_input']['mention_type'] = 'activitypub_ul';
+ } else {
+ error_log( 'AP: Public mention : ' . $to->user_login );
+ $postdata['post_status'] = 'public';
+ $postdata['meta_input']['ap_object'] = \serialize( $object );
+ $postdata['meta_input']['mention_type'] = 'activitypub_public';//or just 'activitypub'
+ }
- $state = \wp_new_comment( $commentdata, true );
+ } else {
+ //Don't know wtf would end up here
+ error_log( 'AP: WTF : ' . $to->user_login );
+ }
+ $post_id = \wp_insert_post($postdata);
+
+ if( !is_wp_error($post_id) ) {
+ error_log($post_id);
+ wp_send_json_success(array('post_id' => $post_id), 200);
+ } else {
+ error_log($post_id->get_error_message());
+ wp_send_json_error($post_id->get_error_message());
+ }
+ }
- // re-add flood control
- \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
}
+
+ /**
+ * preprocess local comments for federated replies
+ */
+ public static function preprocess_comment_handler( $commentdata ) {
+
+ //should only process replies from local actors
+ if ( isset( $commentdata['user_id'] ) ) {
+ //\error_log( 'is_local user' );
+
+ //federate direct replies to post
+ //inReplyTo source_url //delete - comment_post_ID, comment_parent
+
+ //federate replies to foreignAP replies
+ if ( !empty( $commentdata['comment_parent'] ) ) {
+ //has a parent comment
+ $activitypub = get_comment_meta( $commentdata['comment_parent'], 'protocol', true );//needed?
+ $source_url = get_comment_meta( $commentdata['comment_parent'], 'source_url', true );
+
+ if ( $activitypub && $source_url ) {
+ //parent comment is federated
+ // set url to author_url, (user_url can be anything)
+ $commentdata['comment_author_url'] = get_author_posts_url($commentdata['user_id']);
+ $commentdata['comment_type'] = 'activitypub';
+ //error_log('preprocess_comment_handler: should only local');
+ if ( $source_url ) {
+ $op_url = \get_comment_author_url( $commentdata['comment_parent'] );
+ // error_log('preprocess_comment_handler:inReplyTo: ' . $op_url);
+ $commentdata['comment_meta']['replyTo'] = $op_url;
+ $commentdata['comment_meta']['inReplyTo'] = $source_url ;
+ }
+ }
+ }
+ }
+ return $commentdata;
+ }
+
+ /**
+ * postprocess_comment_handler for federating replies and inbox-forwarding
+ */
+ public static function postprocess_comment_handler( $comment_id, $comment_approved, $commentdata ) {
+
+ \error_log( 'postprocess_comment_handler: comment_status: ' . $comment_approved );
+ if ( $commentdata['comment_type'] === 'activitypub' ) {
+ if ( $comment_approved !== 0 ) {
+ // ^^^Remove after testing for lax moderation settings
+ // TODO determine if pseudo email (webfinger) would change how comments are treated
+
+ //error_log( 'postprocess_comment_handler: comment_approved');
+ //should only federate replies to federated actors
+ $ap_object = unserialize( \get_comment_meta( $comment_id, 'ap_object', true ) );
+ // $replyto = get_comment_meta( $comment_id, 'replyto', true );
+
+ //inbox forward prep
+ if ( !empty( $ap_object ) ) {
+ //if is foreign user (has ap_object)
+ error_log( 'postprocess_comment_handler: ap_object: ' );
+ error_log( print_r( $ap_object, true ) );
+ // TODO verify that deduplication check happens at object create.
+
+ //if to/cc/audience contains local followers collection
+ $local_user = \get_comment_author_url( $comment_id );
+ $is_local_user = \Activitypub\url_to_authorid( $commentdata['comment_author_url'] );
+ if ( $is_local_user != 0 ) {
+ if ( in_array( $local_user, $ap_object['to'] )
+ || in_array( $local_user, $ap_object['cc'] )
+ || in_array( $local_user, $ap_object['audience'] )
+ || in_array( $local_user, $ap_object['tag'] )
+ ) {
+ //if inReplyTo, object, target and/or tag are (local-wp) objects
+ //if ( str_contains( $ap_object['audience'], site_url() ) ) {
+ error_log('postprocess_comment_handler: do_inbox_forward_activity');
+ //\ActivityPub\Activity_Dispatcher::inbox_forward_activity( $comment_id );
+ \wp_schedule_single_event( \time(), 'activitypub_send_comment_activity', array( $comment_id ) );
+ //\wp_schedule_single_event( \time(), 'activitypub_inbox_forward_activity', array( $comment->comment_ID ) );//deprecated, was creating duplicates
+ //}
+ //original object
+ // $ap_object['object'] / $ap_object->object
+ }
+ }
+ } else {
+ //error_log( 'postprocess_comment_handler:prep_reply');
+ //error_log( print_r( $commentdata, true ) );
+ //should only federate replies from local actors
+ if ( ( $commentdata['user_id'] !== 0 ) && ( $commentdata['comment_type'] === 'activitypub') ) {
+ \error_log( 'postprocess_comment_handler: federate a reply' );
+ //error_log('federate reply comment, to post collection?');
+ //\ActivityPub\Activity_Dispatcher::send_comment_activity( $comment_id ); // performance > followers collection
+ \wp_schedule_single_event( \time(), 'activitypub_send_comment_activity', array( $comment_id ) );
+
+ }
+ }
+ } else {
+ error_log( 'postprocess_comment_handler: moderation pending');
+ }
+ }
+ /*
+ When Activities are received in the inbox, the server needs to forward these to recipients that the origin was unable to deliver them to.
+ To do this, the server MUST target and deliver to the values of to, cc, and/or audience if and only if all of the following are true:
+
+This is the first time the server has seen this Activity.
+The values of to, cc, and/or audience contain a Collection owned by the server. (user followers collection)
+The values of inReplyTo, object, target and/or tag are objects owned by the server.
+The server SHOULD recurse through these values to look for linked objects owned by the server,
+and SHOULD set a maximum limit for recursion (ie. the point at which the thread is so deep the recipients
+followers may not mind if they are no longer getting updates that don't directly involve the recipient).
+The server MUST only target the values of to, cc, and/or audience on the original object being forwarded, and not pick up
+any new addressees whilst recursing through the linked objects (in case these addressees were purposefully amended by or via the client).
+*/
+ // if ( !empty( $ap_object) && ( $commentdata->comment_type == 'activitypub') ) {
+
+ // error_log('federate reply comment, to post collection?');
+ // \ActivityPub\Activity_Dispatcher::inbox_forward_activity($comment_id);
+ // //\wp_schedule_single_event( \time(), 'activitypub_send_comment_activity', array( $comment->comment_ID ) );
+
+ // }
+ }
+
+ public static function comments_styles() {
+ echo '';
+ }
+
+ //\add_action( 'init', '\Activitypub\add_rewrite_rules', 1 );
+ public static function filter_private_messages( $query ) {
+ $ap_public_comments = array( 'activitypub_ul', 'activitypub' );
+ $ap_private_comments = array( 'activitypub_dm', 'activitypub_fo' );
+ //make public static constants for public/private comment_types
+
+ //if register setting for public comment moderation or not? if not merge arrays
+ $ap_types = array_merge( $ap_public_comments, $ap_private_comments );
+ if (is_admin()){
+
+ $current_screen = get_current_screen();
+ if( $current_screen->parent_base === 'activitypub-mentions-list'){
+ //echo "current_screen
"; print_r( $current_screen ); echo "
";
+ //echo "query
"; print_r( $query ); echo "
";
+ //$query->set( 'post_status', 'private_message' );
+ // $query->query_vars['post_type'] = 'activitypub_messages';
+ // $query->query_vars['post_status'] = 'private_message';
+ //$query->query_vars['type__not_in'] = $ap_types;
+ //$query->query_vars['type__not_in'] = in_array( $query->query_vars['type__not_in'], $ap_types);
+ }
+ }
+ }
+
+ // public static function count_comments( $count, $post_id ) {
+ // $ap_public_comments = array( 'activitypub_ul', 'activitypub' );
+ // $ap_private_comments = array( 'activitypub_dm', 'activitypub_fo' );
+ // $ap_types = array_merge( $ap_public_comments, $ap_private_comments );
+ // $current_screen = get_current_screen();
+ // //$comments_count = wp_count_comments();
+ //
+ // if( !is_null( $current_screen ) ){
+ // if( $current_screen->id === 'edit-comments' ){
+ // // echo " count_comments
";
+ // // echo ""; print_r( $count ); echo "
";
+ // //$query->query_vars['type__not_in'] = in_array( $query->query_vars['type__not_in'], $ap_types);
+ // }
+ // }
+ // if ( 0 === $post_id ){
+ // var_dump( 'count_comments site' );
+ // // echo ""; print_r( $count ); echo "
";
+ // }
+ // }
+
+ // public static function ap_personal_comment_list($clauses){
+ // if ( is_admin() ) {
+ // global $current_user, $wpdb;
+ // $clauses['join'] = "wp_posts";
+ // $clauses['where'] .= " AND wp_posts.post_author = ".$current_user->ID." AND wp_comments.comment_post_ID = wp_posts.ID";
+ // };
+ // // echo " comments_clauses
";
+ // // echo ""; print_r( $clauses ); echo "
";
+ // return $clauses;
+ // }
+
+
+ //\add_action( 'pre_comment_approved', array( '\Activitypub\Rest\Inbox', 'filter_comments' ), 11, 2 );
+ // public static function filter_comments( $approved, $data ) {
+ // /* only allow 'my_custom_comment_type' when is required explicitly */
+ // //echo ""; print_r( $query ); echo "
";
+ // return isset($data['comment_type']) && $data['comment_type'] === 'activitypub_dm'
+ // ? 1
+ // : $approved;
+ // // array( 'activitypub_dm', 'activitypub_fo' )
+ // }
}
diff --git a/includes/rest/class-nodeinfo.php b/includes/rest/class-nodeinfo.php
index 5464e082..ad8a9240 100644
--- a/includes/rest/class-nodeinfo.php
+++ b/includes/rest/class-nodeinfo.php
@@ -27,6 +27,7 @@ public static function register_routes() {
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'discovery' ),
+ 'permission_callback' => '__return_true',
),
)
);
@@ -36,6 +37,7 @@ public static function register_routes() {
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo' ),
+ 'permission_callback' => '__return_true',
),
)
);
@@ -45,6 +47,7 @@ public static function register_routes() {
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo2' ),
+ 'permission_callback' => '__return_true',
),
)
);
diff --git a/includes/rest/class-outbox.php b/includes/rest/class-outbox.php
index 65cc3baa..365d0bcc 100644
--- a/includes/rest/class-outbox.php
+++ b/includes/rest/class-outbox.php
@@ -26,6 +26,18 @@ public static function register_routes() {
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox' ),
'args' => self::request_parameters(),
+ 'permission_callback' => '__return_true',
+ ),
+ )
+ );
+
+ \register_rest_route(
+ 'activitypub/1.0', '/post/(?P\d+)/replies', array(
+ array(
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => array( '\Activitypub\Rest\Outbox', 'post_replies' ),
+ 'args' => self::request_parameters(),
+ 'permission_callback' => '__return_true',
),
)
);
@@ -40,9 +52,9 @@ public static function register_routes() {
public static function user_outbox( $request ) {
$user_id = $request->get_param( 'id' );
$author = \get_user_by( 'ID', $user_id );
-
+// /with_replies include comments
if ( ! $author ) {
- return new \WP_Error( 'rest_invalid_param', \__( 'User not found', 'activitypub' ), array(
+ return new \WP_Error( 'rest_invalid_param', __( 'User not found', 'activitypub' ), array(
'status' => 404,
'params' => array(
'user_id' => \__( 'User not found', 'activitypub' ),
@@ -51,7 +63,8 @@ public static function user_outbox( $request ) {
}
$page = $request->get_param( 'page', 0 );
-
+ //$page = $request->get_param( 'page', true );
+ error_log('outbox');
/*
* Action triggerd prior to the ActivityPub profile being created and sent to the client
*/
@@ -66,15 +79,43 @@ public static function user_outbox( $request ) {
$json->type = 'OrderedCollectionPage';
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/outbox" ); // phpcs:ignore
- $count_posts = \wp_count_posts();
- $json->totalItems = \intval( $count_posts->publish ); // phpcs:ignore
+ $count_posts = \count_user_posts( $user_id );// get allowed post types, public_only = true
+ $json->totalItems = \intval( $count_posts ); // phpcs:ignore
$posts = \get_posts( array(
'posts_per_page' => 10,
'author' => $user_id,
'offset' => $page * 10,
) );
+ $comments = \get_comments(
+ array(
+ 'number' => 10,
+ 'paged' => true,
+ 'user_id' => $user_id,
+ 'status' => 'approved',
+ 'offset' => $page * 10,
+ )
+ );
+ //error_log('$posts: ' . print_r($posts, true ) );
+// add_query_arg( array(
+// 'page' => 'true',
+// 'max_id' => $posts[9]->ID,//nextß
+// 'min_id' => $posts[0]->ID,//prev
+// ), $json->partOf );
+ //if ( ( \ceil ( $json->totalItems / 10 ) ) - 1 > $page ) { // phpcs:ignore
+ // $json->next = add_query_arg( array(
+ // 'max_id' => $posts[9]->ID,//nextß
+ // 'page' => 'true',
+ // ), $json->partOf );
+ // $json->prev = add_query_arg( array(
+ // 'min_id' => $posts[0]->ID,//prev
+ // 'page' => 'true',
+ // ), $json->partOf );
+ //}
+ // if ( ( \ceil ( $json->totalItems / 10 ) ) - 1 > $page ) { // phpcs:ignore
+ // $json->next = \add_query_arg( 'page', ++$page, $json->partOf ); // phpcs:ignore
+ // }
$json->first = \add_query_arg( 'page', 0, $json->partOf ); // phpcs:ignore
$json->last = \add_query_arg( 'page', ( \ceil ( $json->totalItems / 10 ) ) - 1, $json->partOf ); // phpcs:ignore
@@ -88,6 +129,12 @@ public static function user_outbox( $request ) {
$activitypub_activity->from_post( $activitypub_post->to_array() );
$json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
}
+ foreach ( $comments as $comment ) {
+ $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() );
+ $json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
+ }
// filter output
$json = \apply_filters( 'activitypub_outbox_array', $json );
@@ -104,6 +151,90 @@ public static function user_outbox( $request ) {
return $response;
}
+ /**
+ * Renders the replies collection for a post
+ *
+ * @param WP_REST_Request $request
+ * @return WP_REST_Response
+ */
+ public static function post_replies( $request ) {
+ $post_id = $request->get_param( 'id' );
+ $comments = \get_comments( array('post_id' => $post_id) );
+
+ if ( ! $comments ) {
+ return new \WP_Error( 'rest_invalid_param', __( 'No comments found', 'activitypub' ), array(
+ 'status' => 404,
+ 'params' => array(
+ 'post_id' => \__( 'No comments found', 'activitypub' ),
+ ),
+ ) );
+ }
+
+ $page = $request->get_param( 'page', 0 );
+ error_log('comments');
+ /*
+ * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ */
+ \do_action( 'activitypub_replies_pre' );
+
+ $json = new \stdClass();
+
+ $json->{'@context'} = \Activitypub\get_context();
+ $json->id = \home_url( \add_query_arg( null, null ) );
+ //$json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
+ //$json->actor = \get_author_posts_url( $user_id );
+ $json->type = 'CollectionPage';
+
+ $json->partOf = \get_rest_url( null, "/activitypub/1.0/post/$post_id/replies" ); // phpcs:ignore
+
+ $count_comments = \get_comments_number( $post_id );// get allowed post types, public_only = true
+ $json->totalItems = \intval( $count_comments ); // phpcs:ignore
+
+ // $posts = \get_posts( array(
+ // 'posts_per_page' => 10,
+ // 'author' => $user_id,
+ // 'offset' => $page * 10,
+ // ) );
+ /*add_query_arg( array(
+ 'page' => 'true',
+ 'max_id' => 'value2',//next
+ 'min_id' => 'value2',//prev
+ ), $json->partOf );
+ */
+ $json->first = \add_query_arg( 'page', 0, $json->partOf ); // phpcs:ignore
+ $json->last = \add_query_arg( 'page', ( \ceil ( $json->totalItems / 10 ) ) - 1, $json->partOf ); // phpcs:ignore
+
+ if ( ( \ceil ( $json->totalItems / 10 ) ) - 1 > $page ) { // phpcs:ignore
+ $json->next = \add_query_arg( 'page', ++$page, $json->partOf ); // phpcs:ignore
+ }
+
+ foreach ( $comments as $comment ) {
+ $activitypub_comment = new \Activitypub\Model\Comment( $comment );
+ $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_NONE );
+
+ $json->orderedItems[] = $activitypub_comment->to_array(); // phpcs:ignore
+
+ // $activitypub_post = new \Activitypub\Model\Post( $post );
+ // $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_NONE );
+ // $activitypub_activity->from_post( $activitypub_post->to_array() );
+ // $json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
+ }
+
+ // filter output
+ $json = \apply_filters( 'activitypub_comment_array', $json );
+
+ /*
+ * Action triggerd after the ActivityPub profile has been created and sent to the client
+ */
+ \do_action( 'activitypub_comment_post' );
+
+ $response = new \WP_REST_Response( $json, 200 );
+
+ $response->header( 'Content-Type', 'application/activity+json' );
+
+ return $response;
+ }
+
/**
* The supported parameters
*
@@ -124,3 +255,144 @@ public static function request_parameters() {
return $params;
}
}
+
+/**
+ * ActivityPub Replies CollectionPage REST-Class
+ *
+ * @author Django Doucet
+ *
+ * @see https://www.w3.org/TR/activitypub/#outbox
+ */
+class Post {
+ /**
+ * Initialize the class, registering WordPress hooks
+ */
+ public static function init() {
+ \add_action( 'rest_api_init', array( '\Activitypub\Rest\Post', 'register_routes' ) );
+ }
+
+ /**
+ * Register routes
+ */
+ public static function register_routes() {
+ \register_rest_route(
+ 'activitypub/1.0', '/post/(?P\d+)/replies', array(
+ array(
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => array( '\Activitypub\Rest\Post', 'post_replies' ),
+ 'args' => self::request_parameters(),
+ ),
+ )
+ );
+ }
+
+ /**
+ * Renders the user-outbox
+ *
+ * @param WP_REST_Request $request
+ * @return WP_REST_Response
+ */
+ public static function post_replies( $request ) {
+ $post_id = $request->get_param( 'id' );
+ $comments = \get_comments( 'post_id', $post_id );
+
+ if ( ! $comments ) {
+ return new \WP_Error( 'rest_invalid_param', __( 'No comments found', 'activitypub' ), array(
+ 'status' => 404,
+ 'params' => array(
+ 'post_id' => \__( 'No comments found', 'activitypub' ),
+ ),
+ ) );
+ }
+
+ $page = $request->get_param( 'page', 0 );
+ error_log('comments');
+ /*
+ * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ */
+ \do_action( 'activitypub_replies_pre' );
+
+ $json = new \stdClass();
+
+ $json->{'@context'} = \Activitypub\get_context();
+ $json->id = \home_url( \add_query_arg( null, null ) );
+ //$json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
+ //$json->actor = \get_author_posts_url( $user_id );
+ $json->type = 'CollectionPage';
+ if ( ( \ceil ( $json->totalItems / 10 ) ) - 1 > $page ) { // phpcs:ignore
+ $json->next = \add_query_arg( 'page', ++$page, $json->partOf ); // phpcs:ignore
+ }
+ $json->partOf = \get_rest_url( null, "/activitypub/1.0/post/$post_id/replies" ); // phpcs:ignore
+
+ $count_comments = \get_comments_number( $post_id );// get allowed post types, public_only = true
+ $json->totalItems = \intval( $count_comments ); // phpcs:ignore
+
+ // $posts = \get_posts( array(
+ // 'posts_per_page' => 10,
+ // 'author' => $user_id,
+ // 'offset' => $page * 10,
+ // ) );
+/*add_query_arg( array(
+ 'page' => 'true',
+ 'max_id' => 'value2',//next
+ 'min_id' => 'value2',//prev
+), $json->partOf );
+*/
+ $json->first = \add_query_arg( 'page', 0, $json->partOf ); // phpcs:ignore
+ $json->last = \add_query_arg( 'page', ( \ceil ( $json->totalItems / 10 ) ) - 1, $json->partOf ); // phpcs:ignore
+
+ if ( ( \ceil ( $json->totalItems / 10 ) ) - 1 > $page ) { // phpcs:ignore
+ $json->next = \add_query_arg( 'page', ++$page, $json->partOf ); // phpcs:ignore
+ }
+
+ foreach ( $comments as $comment ) {
+ $activitypub_comment = new \Activitypub\Model\Comment( $comment );
+ //$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_NONE );
+
+ $json->orderedItems[] = $activitypub_comment->to_array(); // phpcs:ignore
+ }
+
+ // filter output
+ $json = \apply_filters( 'activitypub_comment_array', $json );
+
+ /*
+ * Action triggerd after the ActivityPub profile has been created and sent to the client
+ */
+ \do_action( 'activitypub_comment_post' );
+
+ $response = new \WP_REST_Response( $json, 200 );
+
+ $response->header( 'Content-Type', 'application/activity+json' );
+
+ return $response;
+ }
+
+ /**
+ * The supported parameters
+ *
+ * @return array list of parameters
+ */
+ public static function request_parameters() {
+ $params = array();
+
+ // $params['page'] = array(
+ // 'type' => 'integer',
+ // );
+
+ // $params['id'] = array(
+ // 'required' => true,
+ // 'type' => 'integer',
+ // );
+
+ return $params;
+ }
+
+ /**
+ * [post_replies_endpoint description]
+ * @return array Post replies CollectionPage
+ */
+ // public function post_replies_endpoint( $vars ) {
+ // if( isset( $vars['replies'] ) ) $vars['replies'] = true;
+ // return $vars;
+ // }
+}
diff --git a/includes/rest/class-server.php b/includes/rest/class-server.php
index 6c68621c..c19eadf0 100644
--- a/includes/rest/class-server.php
+++ b/includes/rest/class-server.php
@@ -18,9 +18,24 @@ class Server extends \WP_REST_Server {
public function dispatch( $request ) {
$content_type = $request->get_content_type();
- // check for content-sub-types like 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
- if ( \preg_match( '/application\/([a-zA-Z+_-]+\+)json/', $content_type['value'] ) ) {
- $request->set_header( 'Content-Type', 'application/json' );
+ if ( isset( $content_type['value'] ) ) {
+ // check for content-sub-types like 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
+ if ( \preg_match( '/application\/([a-zA-Z+_-]+\+)json/', $content_type['value'] ) ) {
+ $request->set_header( 'Content-Type', 'application/json' );
+ }
+ }
+
+ $user_agent = $request->get_header('user_agent');
+
+ if ( \Activitypub\str_contains( $user_agent, 'MastoPeek')
+ || \Activitypub\str_contains( $user_agent, 'Python' )
+ || \Activitypub\str_contains( $user_agent, 'fediverse.space' )
+ || \Activitypub\str_contains( $user_agent, 'Friendica' ) ) {
+ \error_log( 'Crawler: ' . $user_agent );
+ //$request->set_header('HTTP1.1 404 Not Found', true, 404);
+ $request->set_header('Location: https://layer8.space/', true, 301);
+ $request->set_body(null);
+ return new \WP_REST_Response($request, 404);
}
// make request filterable
diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php
index 2ffe7590..f6280ad2 100644
--- a/includes/rest/class-webfinger.php
+++ b/includes/rest/class-webfinger.php
@@ -15,6 +15,7 @@ class Webfinger {
public static function init() {
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Webfinger', 'register_routes' ) );
\add_action( 'webfinger_user_data', array( '\Activitypub\Rest\Webfinger', 'add_webfinger_discovery' ), 10, 3 );
+ //\add_action( 'webfinger_lookup', array( '\Activitypub\Rest\Webfinger', 'webfinger_lookup' ), 10, 3 );
}
/**
@@ -27,6 +28,7 @@ public static function register_routes() {
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Webfinger', 'webfinger' ),
'args' => self::request_parameters(),
+ 'permission_callback' => '__return_true',
),
)
);
diff --git a/includes/table/followers-list.php b/includes/table/followers-list.php
index 81444ee7..f0daf424 100644
--- a/includes/table/followers-list.php
+++ b/includes/table/followers-list.php
@@ -8,6 +8,7 @@
class Followers_List extends \WP_List_Table {
public function get_columns() {
return array(
+ 'cb' => '',
'identifier' => \__( 'Identifier', 'activitypub' ),
);
}
@@ -16,6 +17,113 @@ public function get_sortable_columns() {
return array();
}
+ /**
+ * Render the bulk edit checkbox
+ *
+ * @param array $item
+ *
+ * @return string
+ */
+ function column_cb( $item ) {
+ return sprintf(
+ '', $item['identifier']
+ );
+ }
+
+ /**
+ * Method for name column
+ *
+ * @param array $item an array of DB data
+ *
+ * @return string
+ */
+ function column_message($item) {
+ //$reply_nonce = wp_create_nonce( 'activitypub_delete_follower' );
+ $delete_nonce = wp_create_nonce( 'activitypub_delete_follower' );
+
+ $actions = array(
+ // 'reply' => sprintf('Reply',
+ // esc_attr($_REQUEST['page']),
+ // 'reply',
+ // $item['ap_message_id'],
+ // $reply_nonce
+ // ),
+ 'delete' => sprintf('Delete',
+ esc_attr($_REQUEST['page']),
+ 'delete',
+ $item['identifier'],
+ $delete_nonce
+ ),
+ );
+ return sprintf('%1$s %2$s', $item['identifier'], $this->row_actions($actions) );
+ }
+
+ /**
+ * Delete a message.
+ *
+ * @param int $id message ID
+ */
+ public static function delete_follower( $id ) {
+ $author = wp_get_current_user();
+ \Activitypub\Peer\Followers::remove_follower( $id, $author->ID );
+ }
+
+ /**
+ * Returns an associative array containing the bulk action
+ *
+ * @return array
+ */
+ public function get_bulk_actions() {
+ $actions = [
+ 'bulk-delete' => 'Bulk delete',
+ ];
+
+ return $actions;
+ }
+
+ public function process_bulk_action() {
+
+ $action = $this->current_action();
+ if ( isset( $_REQUEST['_wpnonce'] ) ) {
+ $nonce = \esc_attr( $_REQUEST['_wpnonce'] );
+ }
+
+ switch ($action) {
+ case 'delete':
+ if ( wp_verify_nonce( $nonce, 'activitypub_delete_follower' ) ) {
+ self::delete_follower( absint( $_GET['identifier'] ) );
+ }
+ break;
+ case 'bulk-delete':
+ $ids = esc_sql( $_GET['selected'] );
+ foreach ( $ids as $id ) {
+ self::delete_follower( $id );
+ }
+ break;
+ case 'status':
+ $ids = esc_sql( $_GET['selected'] );
+ foreach ( $ids as $id ) {
+ $comment = \wp_set_comment_status( $id, 'unread', false );
+ error_log( print_r( $comment, true ) );
+ //$comment->comment_approved = 'unread';
+ }
+ break;
+ case 'reply':
+ if ( ! wp_verify_nonce( $nonce, 'activitypub_reply_message' ) ) {
+ die( 'Go get a life script kiddies' );
+ } else {
+ $content = '';
+ $editor_id = 'mycustomeditor';
+ $settings = array( 'media_buttons' => false );
+
+ wp_editor( $content, $editor_id, $settings ); }
+ break;
+ default:
+ break;
+ }
+
+ }
+
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array();
@@ -23,10 +131,15 @@ public function prepare_items() {
$this->process_action();
$this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() );
+ $this->process_bulk_action();
+
$this->items = array();
foreach ( \Activitypub\Peer\Followers::get_followers( \get_current_user_id() ) as $follower ) {
- $this->items[]['identifier'] = \esc_attr( $follower );
+ $this->items[] = array(
+ 'cb' => 'callback',
+ 'identifier' => \esc_attr( $follower ),
+ );
}
}
diff --git a/includes/table/mentions-list.php b/includes/table/mentions-list.php
new file mode 100644
index 00000000..80a49fbe
--- /dev/null
+++ b/includes/table/mentions-list.php
@@ -0,0 +1,262 @@
+ __( 'Mention', 'activitypub' ), //singular name of the listed records
+ 'plural' => __( 'Mentions', 'activitypub' ), //plural name of the listed records
+ 'ajax' => true //should this table support ajax?
+ ] );
+
+ }
+
+ public function no_items() {
+ _e( 'You have no mentions.', 'activitypub' );
+ }
+
+ public function get_columns() {
+ $columns = [
+ 'cb' => '',
+ 'type' => \__( 'Type', 'activitypub' ),
+ 'actor' => \__( 'Actor', 'activitypub' ),
+ 'mention' => \__( 'Mention', 'activitypub' ),
+ 'date' => \__( 'Submitted', 'activitypub' )
+ ];
+
+ return $columns;
+ }
+
+ /**
+ * Render a column when no column specific method exists.
+ *
+ * @param array $item
+ * @param string $column_name
+ *
+ * @return mixed
+ */
+ public function column_default( $item, $column_name ) {
+ switch ( $column_name ) {
+ case 'cb':
+ case 'type':
+ case 'actor':
+ case 'mention':
+ case 'ap_mention_id':
+ case 'date':
+ return $item[ $column_name ];
+ default:
+ return print_r( $item, true ); //Show the whole array for troubleshooting purposes
+ }
+ }
+
+ /**
+ * Columns to make sortable.
+ * TODO: implement actual sort
+ *
+ * @return array
+ */
+ public function get_sortable_columns() {
+ $sortable_columns = array(
+ // 'actor' => array( 'identifier', true ),
+ // 'date' => array( 'date', true ),
+ );
+ return $sortable_columns;
+ }
+
+
+ /**
+ * Render the bulk edit checkbox
+ *
+ * @param array $item
+ *
+ * @return string
+ */
+ function column_cb( $item ) {
+ return sprintf(
+ '',
+ $item['ap_mention_id']
+ );
+ }
+
+ function single_row( $item ) {
+ //global $post, $comment;
+
+ $post_id = $item['ap_mention_id'];
+ $post_type = $item['type'];
+ $post = get_post( $item['ap_mention_id'] );
+
+ //$local_user = get_post_meta( post_id, 'local_user', true );
+ $post_status = $post->post_status;
+ $posts_class = implode(" ",get_post_class( $post_status, $post_id ));
+ //$posts_class = get_post_class( $post_status, $post_id );
+
+ $this->user_can = current_user_can( 'edit_post', $post_id );
+
+ echo "";
+ echo $this->single_row_columns( $item );
+ echo " \n";
+
+ }
+
+ /**
+ * Method for name column
+ *
+ * @param array $item an array of DB data
+ *
+ * @return string
+ */
+ function column_mention($item) {
+ $reply_nonce = wp_create_nonce( 'activitypub_reply_mention' );
+ $delete_nonce = wp_create_nonce( 'activitypub_delete_mention' );
+
+ $actions = array(
+ 'reply' => sprintf('Reply',
+ admin_url('post-new.php'),
+ 'activitypub_mentions',
+ $item['_author_url'],
+ $item['ap_mention_id'],
+ //$item['audience'],//limit audience in UI, but also perform checks later
+ $reply_nonce
+ ),
+ // 'oldreply' => sprintf('Reply',
+ // esc_attr($_REQUEST['page']),
+ // 'reply',
+ // $item['ap_mention_id'],
+ // $reply_nonce
+ // ),
+ 'quickreply' => sprintf('',
+ $item['ap_mention_id'],
+ $reply_nonce
+ ),
+ 'delete' => sprintf('Delete',
+ esc_attr($_REQUEST['page']),
+ 'delete',
+ $item['ap_mention_id'],
+ $delete_nonce
+ ),
+ );
+ return sprintf('%1$s %2$s', $item['mention'], $this->row_actions($actions) );
+ }
+
+ public function process_bulk_action() {
+
+ $action = $this->current_action();
+ if ( isset( $_REQUEST['_wpnonce'] ) ) {
+ $nonce = \esc_attr( $_REQUEST['_wpnonce'] );
+ }
+
+ switch ($action) {
+ case 'bulk-delete':
+ if ( wp_verify_nonce( $nonce, 'activitypub_delete_mention' ) ) {
+ $ids = esc_sql( $_GET['selected'] );
+ foreach ( $ids as $id ) {
+ self::delete_mention( $id );
+ }
+ }
+ break;
+ // case 'status':
+ // $ids = esc_sql( $_GET['selected'] );
+ // foreach ( $ids as $id ) {
+ // $comment = \wp_set_comment_status( $id, 'unread', false );
+ // error_log( print_r( $comment, true ) );
+ // //$comment->comment_approved = 'unread';
+ // }
+ // break;
+ case 'reply':
+ if ( ! wp_verify_nonce( $nonce, 'activitypub_reply_mention' ) ) {
+ die( 'Get a life script kiddies' );
+ } else {
+ $content = '';
+ $editor_id = 'mycustomeditor';
+ $settings = array( 'media_buttons' => false );
+
+ wp_editor( $content, $editor_id, $settings );
+ }
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ /**
+ * Returns an associative array containing the bulk action
+ *
+ * @return array
+ */
+ public function get_bulk_actions() {
+ $actions = [
+ // 'delete' => 'Delete',
+ 'bulk-delete' => 'Delete',
+ // 'status' => 'Change status'
+ ];
+
+ return $actions;
+ }
+
+ /**
+ * Delete a mention.
+ *
+ * @param int $id mention ID
+ */
+ public static function delete_mention( $id ) {
+ wp_delete_post( $id );
+ }
+
+ public function prepare_items() {
+ $columns = $this->get_columns();
+ //$hidden = array('type');
+ $hidden = array();
+
+ $this->process_action();
+ $this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() );
+
+ /** Process bulk action */
+ $this->process_bulk_action();
+
+ $this->items = array();
+
+ $mentions = \Activitypub\Peer\Mentions::get_mentions();
+
+ //echo human_time_diff( '2020-05-28 10:14:50', current_datetime('Y-m-d H:i:s') );
+ echo 'DEBUG
'; print_r($mentions); echo '
';
+
+ foreach ( $mentions as $mention ) :
+ $this->items[] = array(
+ 'cb' => 'callback',
+ 'ap_mention_id' => $mention->ID,
+ 'actor' => '' . get_post_meta( $mention->ID, '_author', true ) . '',
+ 'mention' => $mention->post_content,
+ 'type' => $mention->post_status,
+ 'date' => '' . $mention->post_date . '', //https://www.lightningrank.com/how-to-display-wordpress-post-date-as-time-ago-posted-x-days-ago/
+ '_author_url' => get_post_meta( $mention->ID, '_author_url', true ),
+ //'date' => $mention->comment_date,
+ );
+ endforeach;
+
+ }
+
+ //add_action( 'admin_head', 'admin_header' );
+ public function admin_header_parent() {
+ $page = ( isset($_GET['page'] ) ) ? esc_attr( $_GET['page'] ) : false;
+ if( 'activitypub-mentions-list' != $page )
+ return;
+
+ add_action( 'admin_head', array( $this, 'admin_header' ) );
+
+ }
+
+}
+/*
+https://wpengineer.com/2426/wp_list_table-a-step-by-step-guide/
+
+https://www.sitepoint.com/using-wp_list_table-to-create-wordpress-admin-tables/
+
+*/
\ No newline at end of file
diff --git a/templates/audience.php b/templates/audience.php
new file mode 100644
index 00000000..2cea3e19
--- /dev/null
+++ b/templates/audience.php
@@ -0,0 +1,4 @@
+