Compare commits

...

167 commits

Author SHA1 Message Date
Matthias Pfefferle
e9ea9db6a3 small changes 2023-11-07 09:59:02 +01:00
Matthias Pfefferle
7d525a8efe otherwise to_json would not work properly 2023-11-06 14:43:35 +01:00
Matthias Pfefferle
8a5e83b619 remove other query-vars 2023-11-06 14:41:51 +01:00
Matthias Pfefferle
1e5c7675cd never used 2023-11-06 14:40:05 +01:00
Matthias Pfefferle
5b440b3819 remove reply_recipients 2023-11-06 14:38:58 +01:00
Matthias Pfefferle
84e394f9df remove JS calls 2023-11-06 14:36:32 +01:00
Matthias Pfefferle
ebe9af4497 remove replies for now 2023-11-06 14:35:49 +01:00
Matthias Pfefferle
be2d6683bb remove some edit methods 2023-11-06 14:32:24 +01:00
Matthias Pfefferle
24315a41b6 this is never used 2023-11-06 12:32:53 +01:00
Matthias Pfefferle
d12708e746 PHPDoc 2023-11-06 12:23:04 +01:00
Matthias Pfefferle
1353c89b38 move functions 2023-11-06 12:21:46 +01:00
Matthias Pfefferle
2d09cf5b45 fix locale 2023-11-06 12:09:54 +01:00
Django Doucet
15555bdd31 rename get_id method to generate_id 2023-11-04 00:03:42 -06:00
Django Doucet
3c089a6549 remove context property 2023-11-04 00:03:05 -06:00
Matthias Pfefferle
406d03345e
Merge branch 'master' into Comments 2023-10-28 00:12:37 +02:00
Django Doucet
921829f1b4 lint 2023-10-27 15:06:15 -06:00
Django
9da43a9d71
Merge branch 'Automattic:master' into Comments 2023-10-27 14:57:07 -06:00
Django Doucet
2335fb1b47 Schedule comments activities for non-admin users 2023-10-27 14:44:47 -06:00
Django Doucet
e04816a12b Restore and tweak Comment transform 2023-10-27 14:43:56 -06:00
Django Doucet
06be55b61a re-add extract mentions filter 2023-10-27 14:17:53 -06:00
Django Doucet
9d1ba79788 remove dead dispatch action 2023-10-27 14:05:31 -06:00
Django Doucet
5301cdaed7 Init Comments class 2023-10-27 13:53:17 -06:00
Django Doucet
e6473db4f8 Link to comment source as row action 2023-10-27 09:16:29 -06:00
Django Doucet
375886b3de Link remote comments on frontend 2023-10-27 09:07:12 -06:00
Django Doucet
9af54707b9 fix warnings 2023-10-27 08:27:12 -06:00
Django Doucet
8a0e02b39f Merge branch 'master' into Comments 2023-10-27 07:28:21 -06:00
Django
e7c065d173
Merge branch 'Automattic:master' into Comments 2023-10-18 22:19:48 -06:00
Matthias Pfefferle
c7da88a35c
Merge branch 'master' into Comments 2023-10-11 13:03:34 +02:00
Matthias Pfefferle
588c5698de
Merge branch 'master' into Comments 2023-10-09 16:01:14 +02:00
Matthias Pfefferle
e8736c15a9 move functions to transformer class 2023-10-09 12:35:57 +02:00
Matthias Pfefferle
8f03fdeaf8
Merge branch 'master' into Comments 2023-10-07 11:02:21 +02:00
Matthias Pfefferle
a18740c1ef fix PHPCS 2023-10-06 17:46:36 +02:00
Matthias Pfefferle
69a0a90b10 fix tests 2023-10-06 17:43:03 +02:00
Matthias Pfefferle
1a58b69b80 fix tests 2023-10-06 17:37:00 +02:00
Matthias Pfefferle
f92524b384 fixed typo 2023-10-06 17:28:55 +02:00
Matthias Pfefferle
77e611cf2c fix testcase 2023-10-06 16:50:27 +02:00
Matthias Pfefferle
e57932b099 fix unittests 2023-10-06 16:46:01 +02:00
Matthias Pfefferle
0185e38013 fix namespace 2023-10-06 16:39:11 +02:00
Matthias Pfefferle
b9e49d42c3 some more fixes 2023-10-06 16:33:54 +02:00
Matthias Pfefferle
69195f8b3a fixes 2023-10-06 16:10:40 +02:00
Matthias Pfefferle
50f7ae2a04 move some functions 2023-10-06 16:08:15 +02:00
Matthias Pfefferle
a9c1862369 some simplifications 2023-10-06 15:45:58 +02:00
Matthias Pfefferle
b07e756927 use const instead of dirname 2023-10-06 11:38:08 +02:00
Matthias Pfefferle
63a8502205
Merge branch 'master' into Comments 2023-10-05 09:51:43 +02:00
Matthias Pfefferle
110eb409d4
Merge branch 'master' into Comments 2023-10-04 15:48:05 +02:00
Django
92fd280133
Merge branch 'Automattic:master' into Comments 2023-10-03 06:00:10 -07:00
Django Doucet
a78a581b41 enqueue reply script on front end 2023-10-02 18:05:48 -06:00
Django Doucet
f018432033 front-end reply inserts @mentions 2023-10-02 17:48:33 -06:00
Django Doucet
734235b500 Fix comment updated datetime 2023-10-02 09:26:16 -07:00
Django Doucet
bf5cccf685 update post field meta 2023-10-01 12:23:24 -06:00
Django Doucet
f44298f503 reinclude user_id in saved ap_object meta 2023-10-01 12:22:35 -06:00
Django Doucet
641d250a06 remove redundant id check 2023-10-01 12:20:49 -06:00
Django Doucet
7339a6419d Update activitypub_send_comment_activity to include type 2023-10-01 12:18:01 -06:00
Django Doucet
d67556ca33 Adds new helpers for resolving inReplyTo url 2023-10-01 12:09:31 -06:00
Django Doucet
8495fda263 ignore WP_Comment type for now 2023-10-01 12:04:07 -06:00
Django Doucet
133c16dc3e migrate dispatch, migrate comment model to transform 2023-10-01 11:55:44 -06:00
Django Doucet
f808ff33cb Separate comment reply script 2023-10-01 11:38:37 -06:00
Django Doucet
da2495e88f Merge branch 'master' into Comments 2023-09-29 09:47:17 -06:00
Django Doucet
43452e4443 Merge branch 'master' into Comments 2023-08-25 13:34:49 -06:00
Django Doucet
c9c155a858 better actual translation support 2023-03-10 17:11:59 -07:00
Django
af6dd43241
Merge branch 'pfefferle:master' into Comments 2023-03-10 16:34:51 -07:00
Django Doucet
e4c991393d phpcbf 2023-03-10 16:27:35 -07:00
Django Doucet
1927587e95 cleanup 2023-03-10 15:48:09 -07:00
Django Doucet
3017e10e06 fix var name 2023-03-10 15:46:21 -07:00
Django Doucet
86c8148cde cleanup 2023-03-10 15:45:45 -07:00
Django Doucet
9f1b65b7ef Add comment edit datetime 2023-03-10 15:38:52 -07:00
Django Doucet
0ab95add02 Add webfinger filter to comments 2023-03-10 15:26:25 -07:00
Django Doucet
6f55010df2 fix preprocess_comment cap check 2023-03-10 15:23:08 -07:00
Django Doucet
1fe5959960 fix comment model replies property 2023-03-10 14:56:26 -07:00
Django Doucet
d07bb1f808 update comment model 2023-03-10 14:54:50 -07:00
Django Doucet
a058ec0569 Update dispatch comments 2023-03-10 13:52:38 -07:00
Django Doucet
1ac6882f3a rename function, inserts users into reply text 2023-03-10 10:14:46 -07:00
Django Doucet
116070e23c Reply to comments from Dashboard 2023-03-10 08:43:03 -07:00
Django Doucet
30129adac5 Remove remote comments from preprocessing 2023-02-06 22:43:39 -07:00
Django Doucet
e46137a970 Update enqueue scripts 2023-02-06 22:00:37 -07:00
Django Doucet
0525645f7c remove version_check() 2023-02-06 22:00:20 -07:00
Django Doucet
33429cfb83 Remove migrate code 2023-02-05 22:09:52 -07:00
Django Doucet
7e6c112679 Merge branch 'master' into Comments 2023-02-05 22:05:30 -07:00
Django Doucet
2e72e93cbd remove extra curly bracket 2023-02-04 15:37:09 -07:00
Django Doucet
627e5a9c0c Merge branch 'threaded-comments' into Comments 2023-02-04 15:35:01 -07:00
Django
9cab6248e9
Merge pull request #4 from mexon/threaded-comments
Threaded comments
2023-01-29 22:07:32 -07:00
Django Doucet
f7fad1b118 phpcbf 2023-01-29 21:52:19 -07:00
Django Doucet
44d05351eb extract mentions from comment_content 2023-01-29 21:44:52 -07:00
Django Doucet
f943de1e79 remove type and mention meta from comment filters 2023-01-29 21:44:15 -07:00
Django Doucet
aca5e273fe update post meta canonical 2023-01-29 20:23:51 -07:00
Django Doucet
c653575594 Remove migrate code 2023-01-29 20:22:48 -07:00
Django Doucet
a159d70658 delete old js 2023-01-29 17:29:57 -07:00
Django Doucet
80901b5b65 update js to new file 2023-01-29 17:29:45 -07:00
Django Doucet
f9c0edc681 Merge branch 'master' into Comments 2023-01-28 12:50:07 -07:00
Matthew Exon
22e0ddc134 attempt to resolve backwards compatibility issues 2023-01-24 13:55:00 +08:00
Matthew Exon
a9a5b112b0 make filter function static 2023-01-24 13:40:28 +08:00
Matthew Exon
85ca37aa1d fix code smells 2023-01-24 13:40:28 +08:00
Matthew Exon
7fa58cf26c add first unit tests for class inbox 2023-01-24 13:23:23 +08:00
Matthew Exon
8db9be5c2e remove debugging log line 2023-01-24 11:10:01 +08:00
Matthew Exon
6a906e5fe2 refactor support for threaded comments from ActivityPub 2023-01-24 11:09:27 +08:00
Matthew Exon
f1f3c3165d support threaded comments from ActivityPub 2023-01-24 11:07:48 +08:00
Matthias Pfefferle
45b6e63f32 fix phpcs issues 2022-12-23 13:17:12 +01:00
Matthias Pfefferle
6b15d8d158 run phpcs also on pull_requests 2022-12-23 12:14:48 +01:00
Matthias Pfefferle
733bc88faf fix phpcs issues 2022-12-23 12:12:12 +01:00
Matthias Pfefferle
8f6ad230ce Merge branch 'Comments' of https://github.com/mediaformat/wordpress-activitypub into pr/142 2022-12-23 12:10:51 +01:00
Matthias Pfefferle
ae54805cf0 fix phpcs issues 2022-12-23 12:10:42 +01:00
Matthias Pfefferle
2ca6bdc487
remove trailing spaces 2022-12-23 12:04:25 +01:00
Matthias Pfefferle
f1dfd52329
Merge branch 'master' into Comments 2022-12-23 12:03:13 +01:00
Django Doucet
093bc3c88b fix comparison 2022-12-19 14:16:56 -07:00
Django Doucet
b04538a2ab remove comment_type 2022-12-19 14:16:56 -07:00
Eana Hufwe
e48986dd99 Add Custom Post Type support to outbox API 2022-12-19 14:16:55 -07:00
Django Doucet
a6657edd4a fix pagination 2022-12-19 14:16:55 -07:00
Andreas
0f356acee8 restrict html tags after which to detect a hashtag
Hashtags should not be detected after just any html tag - for example not after an opening a or div. To still allow detection at the start of a line, allow specifically p and br to directly precede a hashtag.
2022-12-19 14:16:55 -07:00
Andreas
0b881bb9fd also detect hashtags at the start of a paragraph 2022-12-19 14:16:55 -07:00
Andreas
3f92bcc41b change regex matching potential hashtags
Matches any string starting with '#' and consisting of any number and combination of [A-Za-z0-9_] that is directly followed by whitespace or punctuation. Groups everything after '#' for access in functions using this regex.

This fixes #183 (incomplete links on hashtags containing special characters) by not matching these at all.
2022-12-19 14:16:55 -07:00
Django Doucet
fe7def2c84 Fix js assets enqueue 2022-12-19 14:16:55 -07:00
Django Doucet
5dbf365c58 associate comments to back compat post 2022-12-19 14:16:54 -07:00
Django Doucet
b1f64f6828 fix file path 2022-12-19 14:16:54 -07:00
Django Doucet
d7c9e10c21 Separate file for Comment processing hooks 2022-12-19 14:16:54 -07:00
Django Doucet
18a8752c6a move js file to assets/js 2022-12-19 14:16:54 -07:00
Django Doucet
8cce944edd Post class fix attributes 2022-12-19 14:16:54 -07:00
Django Doucet
1ef6fc21c1 Comments class missing attributes 2022-12-19 14:16:54 -07:00
Django Doucet
f87dbd87be replace ap_comment_id to reuse replytocom var 2022-12-19 14:16:54 -07:00
Django Doucet
de74f1d70b Fix send_delete_activity 2022-12-19 14:16:54 -07:00
Django Doucet
c801d072c8 more style fixes 2022-12-19 14:16:54 -07:00
Django Doucet
852634360b style fix 2022-12-19 14:16:53 -07:00
Django Doucet
055c22e9c3 Add comments view, warnings to migrate page 2022-12-19 14:16:53 -07:00
Django Doucet
8707387ab8 Improve migration UX 2022-12-19 14:16:53 -07:00
Django Doucet
ecbe724b82 code cleanup 2022-12-19 14:16:53 -07:00
Django Doucet
2d0e962263 update included filename 2022-12-19 14:16:53 -07:00
Django Doucet
6a69a40295 Fix announce, clarified language 2022-12-19 14:16:53 -07:00
Django Doucet
63bf62402d regression fix 2022-12-19 14:16:53 -07:00
Django Doucet
aa1b54462e code cleanup 2022-12-19 14:16:53 -07:00
Django Doucet
be9b74478d Migrate tools 2022-12-19 14:16:53 -07:00
4e3069d931 fix dependencies 2022-12-19 14:16:53 -07:00
308df84c28 allow plugins 2022-12-19 14:16:53 -07:00
a5a6967b48 update composer file to fix unit testing 2022-12-19 14:16:53 -07:00
6fe621bf50 version bump 2022-12-19 14:16:53 -07:00
30a37dab6e fix webfinger for email identifiers
fix #152
2022-12-19 14:16:53 -07:00
7c24e994ec fix docker 2022-12-19 14:16:53 -07:00
cbc989fe83 change background image for wp.org 2022-12-19 14:16:53 -07:00
db84a5257a PHPCS fixes 2022-12-19 14:16:53 -07:00
b4e79e1988 fix PHPCS 2022-12-19 14:16:53 -07:00
Django Doucet
a4e8cc7e14 fix cs 2022-12-19 14:16:52 -07:00
Django Doucet
6af56cf82e fix code standards 2022-12-19 14:16:52 -07:00
Django Doucet
36bef694b6 add settings link to plugin page 2022-12-19 14:16:52 -07:00
Django Doucet
3db5fb2626 bugfix 2022-12-19 14:09:55 -07:00
Django Doucet
5d8e3a9ba9 Migrate / Update script 2022-12-19 14:09:55 -07:00
Django Doucet
e5080f8693 code standards cleanup 2022-12-19 14:09:55 -07:00
2d50b44d13 move stale file 2022-12-19 14:09:55 -07:00
Matthias Pfefferle
7fe1ce4888 Create stale.yml 2022-12-19 14:09:54 -07:00
Django Doucet
8e1c9ff6bb Replies Collection, settings, other fixes 2022-12-19 14:09:54 -07:00
Django Doucet
51643142aa coding standards 2022-12-19 14:09:54 -07:00
Django Doucet
73e6be9782 webfinger_extract remove extra param 2022-12-19 14:09:53 -07:00
Django Doucet
8938c67073 Comments update 2022-12-19 14:09:53 -07:00
Matthias Pfefferle
163f2ed013 fix typo 2022-12-19 14:09:53 -07:00
6772d300b4 phpcs fixes 2022-12-19 14:09:53 -07:00
a88eea6c1c Update composer.json 2022-12-19 14:09:53 -07:00
Matthias Pfefferle
395187a717 Update phpcs.xml 2022-12-19 14:09:53 -07:00
Matthias Pfefferle
a41f84e88f Create phpcs.yml 2022-12-19 14:09:53 -07:00
Matthias Pfefferle
dc5ef86887 Update composer.json 2022-12-19 14:09:53 -07:00
Matthias Pfefferle
0462e9d7b0 Update phpunit.yml 2022-12-19 14:09:53 -07:00
Matthias Pfefferle
4c723d1e28 Update composer.json 2022-12-19 14:09:52 -07:00
Matthias Pfefferle
de98e07019 Update composer.json 2022-12-19 14:09:52 -07:00
Matthias Pfefferle
4a4589ae1f Create phpunit.yml 2022-12-19 14:09:52 -07:00
e0730a62be version bump 2022-12-19 14:09:52 -07:00
8733957963 fix #135 2022-12-19 14:09:52 -07:00
338200b386 fix "Follow" issue
fix #133
2022-12-19 14:09:52 -07:00
d452ba8585 change URL to bp_core_get_user_domain 2022-12-19 14:09:52 -07:00
8e98fffab6 Add basic BuddyPress support
fix #122

thanks and props @skysarwer
2022-12-19 14:09:52 -07:00
Matthias Pfefferle
146321102d Delete FUNDING.yml 2022-12-19 14:09:51 -07:00
Django Doucet
17c386cb9d Comments 1 2021-09-06 00:25:02 -06:00
14 changed files with 943 additions and 37 deletions

View file

@ -72,6 +72,7 @@ function plugin_init() {
\add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) );
\add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) );
\add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) );
\add_action( 'init', array( __NAMESPACE__ . '\Comments', 'init' ) );
if ( site_supports_blocks() ) {
\add_action( 'init', array( __NAMESPACE__ . '\Blocks', 'init' ) );

View file

@ -11,10 +11,11 @@ jQuery( function( $ ) {
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
}
} );
$(document).on( 'wp-plugin-install-success', function( event, response ) {
setTimeout( function() {
$( '.activate-now' ).removeClass( 'thickbox open-plugin-details-modal' );
}, 1200 );
} );
} );
} );

View file

@ -2,10 +2,13 @@
namespace Activitypub;
use WP_Post;
use WP_Comment;
use Activitypub\Activity\Activity;
use Activitypub\Collection\Users;
use Activitypub\Collection\Followers;
use Activitypub\Transformer\Factory;
use Activitypub\Transformer\Post;
use Activitypub\Transformer\Comment;
use function Activitypub\is_single_user;
use function Activitypub\is_user_disabled;
@ -30,12 +33,12 @@ class Activity_Dispatcher {
/**
* Send Activities to followers and mentioned users or `Announce` (boost) a blog post.
*
* @param WP_Post $wp_post The ActivityPub Post.
* @param string $type The Activity-Type.
* @param mixed $wp_object The ActivityPub Post.
* @param string $type The Activity-Type.
*
* @return void
*/
public static function send_activity_or_announce( WP_Post $wp_post, $type ) {
public static function send_activity_or_announce( $wp_object, $type ) {
// check if a migration is needed before sending new posts
Migration::maybe_migrate();
@ -43,35 +46,35 @@ class Activity_Dispatcher {
return;
}
$wp_post->post_author = Users::BLOG_USER_ID;
$wp_object->post_author = Users::BLOG_USER_ID;
if ( is_single_user() ) {
self::send_activity( $wp_post, $type );
self::send_activity( $wp_object, $type );
} else {
self::send_announce( $wp_post, $type );
self::send_announce( $wp_object, $type );
}
}
/**
* Send Activities to followers and mentioned users.
*
* @param WP_Post $wp_post The ActivityPub Post.
* @param string $type The Activity-Type.
* @param mixed $wp_object The ActivityPub Post.
* @param string $type The Activity-Type.
*
* @return void
*/
public static function send_activity( WP_Post $wp_post, $type ) {
if ( is_user_disabled( $wp_post->post_author ) ) {
public static function send_activity( $wp_object, $type ) {
if ( is_user_disabled( $wp_object->post_author ) ) {
return;
}
$object = Post::transform( $wp_post )->to_object();
$object = Factory::get_transformer( $wp_object )->to_object();
$activity = new Activity();
$activity->set_type( $type );
$activity->set_object( $object );
$follower_inboxes = Followers::get_inboxes( $wp_post->post_author );
$follower_inboxes = Followers::get_inboxes( $wp_object->post_author );
$mentioned_inboxes = Mention::get_inboxes( $activity->get_cc() );
$inboxes = array_merge( $follower_inboxes, $mentioned_inboxes );
@ -80,19 +83,19 @@ class Activity_Dispatcher {
$json = $activity->to_json();
foreach ( $inboxes as $inbox ) {
safe_remote_post( $inbox, $json, $wp_post->post_author );
safe_remote_post( $inbox, $json, $wp_object->post_author );
}
}
/**
* Send Announces to followers and mentioned users.
*
* @param WP_Post $wp_post The ActivityPub Post.
* @param string $type The Activity-Type.
* @param mixed $wp_object The ActivityPub Post.
* @param string $type The Activity-Type.
*
* @return void
*/
public static function send_announce( WP_Post $wp_post, $type ) {
public static function send_announce( $wp_object, $type ) {
if ( ! in_array( $type, array( 'Create', 'Update' ), true ) ) {
return;
}
@ -101,7 +104,7 @@ class Activity_Dispatcher {
return;
}
$object = Post::transform( $wp_post )->to_object();
$object = Factory::get_transformer( $wp_object )->to_object();
$activity = new Activity();
$activity->set_type( 'Announce' );
@ -110,7 +113,7 @@ class Activity_Dispatcher {
// send only the id
$activity->set_object( $object->get_id() );
$follower_inboxes = Followers::get_inboxes( $wp_post->post_author );
$follower_inboxes = Followers::get_inboxes( $wp_object->post_author );
$mentioned_inboxes = Mention::get_inboxes( $activity->get_cc() );
$inboxes = array_merge( $follower_inboxes, $mentioned_inboxes );
@ -119,7 +122,7 @@ class Activity_Dispatcher {
$json = $activity->to_json();
foreach ( $inboxes as $inbox ) {
safe_remote_post( $inbox, $json, $wp_post->post_author );
safe_remote_post( $inbox, $json, $wp_object->post_author );
}
}
}

View file

@ -4,6 +4,8 @@ namespace Activitypub;
use Activitypub\Signature;
use Activitypub\Collection\Users;
use function Activitypub\is_comment;
/**
* ActivityPub Class
*
@ -92,6 +94,8 @@ class Activitypub {
if ( \is_author() ) {
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php';
} elseif ( is_comment() ) {
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/comment-json.php';
} elseif ( \is_singular() ) {
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-json.php';
} elseif ( \is_home() ) {
@ -114,6 +118,7 @@ class Activitypub {
*/
public static function add_query_vars( $vars ) {
$vars[] = 'activitypub';
$vars[] = 'replytocom';
return $vars;
}
@ -189,9 +194,11 @@ class Activitypub {
* @return string $url
*/
public static function remote_comment_link( $comment_link, $comment ) {
$remote_comment_link = get_comment_meta( $comment->comment_ID, 'source_url', true );
if ( $remote_comment_link ) {
$comment_link = esc_url( $remote_comment_link );
if ( ! is_admin() ) {
$remote_comment_link = get_comment_meta( $comment->comment_ID, 'source_url', true );
if ( $remote_comment_link ) {
$comment_link = esc_url( $remote_comment_link );
}
}
return $comment_link;
}

118
includes/class-comments.php Normal file
View file

@ -0,0 +1,118 @@
<?php
namespace Activitypub;
/**
* Comments Class
*
* @author Django Doucet
*/
class Comments {
/**
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_filter( 'preprocess_comment', array( self::class, 'preprocess_comment' ) );
\add_filter( 'comment_excerpt', array( self::class, 'comment_excerpt' ), 10, 3 );
\add_filter( 'comment_text', array( self::class, 'comment_content_filter' ), 10, 3 );
\add_filter( 'comment_post', array( self::class, 'postprocess_comment' ), 10, 3 );
\add_action( 'edit_comment', array( self::class, 'edit_comment' ), 20, 2 ); //schedule_admin_comment_activity
}
/**
* preprocess local comments for federated replies
*/
public static function preprocess_comment( $commentdata ) {
// only process replies from authorized local actors, for ap enabled post types
if ( 0 === $commentdata['user_id'] ) {
return $commentdata;
}
$user = \get_userdata( $commentdata['user_id'] );
$comment_parent_post = \get_post_type( $commentdata['comment_post_ID'] );
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) );
if ( $user->has_cap( 'publish_post', $commentdata['comment_post_ID'] ) && \in_array( $comment_parent_post, $post_types, true ) ) {
$commentdata['comment_meta']['protocol'] = 'activitypub';
}
return $commentdata;
}
/**
* Filters the comment text to display webfinger in the Recently Published Dashboard Widget.
* comment_excerpt( $comment_excerpt, $comment_id )
*
* doesn't work on received webfinger links as get_comment_excerpt strips tags
* https://developer.wordpress.org/reference/functions/get_comment_excerpt/
* @param string $comment_text
* @param int $comment_id
* @param array $args
* @return void
*/
public static function comment_excerpt( $comment_excerpt, $comment_id ) {
$comment = get_comment( $comment_id );
$comment_excerpt = \apply_filters( 'the_content', $comment_excerpt, $comment );
return $comment_excerpt;
}
/**
* comment_text( $comment )
* Filters the comment text for display.
*
* @param string $comment_text
* @param WP_Comment $comment
* @param array $args
* @return void
*/
public static function comment_content_filter( $comment_text, $comment, $args ) {
$comment_text = \apply_filters( 'the_content', $comment_text, $comment );
$protocol = \get_comment_meta( $comment->comment_ID, 'protocol', true );
// TODO Test if this is returned by Model/Comment
if ( 'activitypub' === $protocol ) {
$updated = \get_comment_meta( $comment->comment_ID, 'activitypub_last_modified', true );
if ( $updated ) {
$format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
$formatted_datetime = \date_i18n( $format, \strtotime( $updated ) );
$iso_date = \wp_date( 'c', \strtotime( $updated ) );
$i18n_text = sprintf(
/* translators: %s: Displays comment last modified date and time */
__( '(Last edited on %s)', 'activitypub' ),
"<time class='modified' datetime='{$iso_date}'>$formatted_datetime</time>"
);
$comment_text .= "<small>$i18n_text<small>";
}
}
return $comment_text;
}
/**
* comment_post()
* postprocess_comment for federating replies and inbox-forwarding
*/
public static function postprocess_comment( $comment_id, $comment_approved, $commentdata ) {
//Administrator role users comments bypass transition_comment_status (auto approved)
$user = \get_userdata( $commentdata['user_id'] );
if (
( 1 === $comment_approved ) &&
\in_array( 'administrator', $user->roles )
) {
// Only needed for Admins (because of transition bypass)
$wp_comment = \get_comment( $comment_id );
\wp_schedule_single_event( \time(), 'activitypub_send_activity', array( $wp_comment, 'Create' ) );
}
}
/**
* edit_comment()
*
* Fires immediately after a comment is updated in the database.
* Fires immediately before comment status transition hooks are fired. (useful only for admin)
*/
public static function edit_comment( $comment_id, $commentdata ) {
update_comment_meta( $comment_id, 'activitypub_last_modified', \wp_date( 'Y-m-d H:i:s' ) );
$user = \get_userdata( $commentdata['user_id'] );
if ( \in_array( 'administrator', $user->roles ) ) {
$wp_comment = \get_comment( $comment_id );
\wp_schedule_single_event( \time(), 'activitypub_send_activity', array( $wp_comment, 'Update' ) );
}
}
}

View file

@ -63,7 +63,7 @@ class Hashtag {
$tag = strtolower( $m[2] );
if ( '/' === $m[1] ) {
// Closing tag.
$i = array_search( $tag, $tag_stack );
$i = array_search( $tag, $tag_stack, true );
// We can only remove the tag from the stack if it is in the stack.
if ( false !== $i ) {
$tag_stack = array_slice( $tag_stack, 0, $i );

View file

@ -17,6 +17,7 @@ class Scheduler {
*/
public static function init() {
\add_action( 'transition_post_status', array( self::class, 'schedule_post_activity' ), 33, 3 );
\add_action( 'transition_comment_status', array( self::class, 'schedule_comment_activity' ), 20, 3 );
\add_action( 'activitypub_update_followers', array( self::class, 'update_followers' ) );
\add_action( 'activitypub_cleanup_followers', array( self::class, 'cleanup_followers' ) );
@ -99,6 +100,36 @@ class Scheduler {
);
}
/**
* Schedule Comment Activities
*
* transition_comment_status()
*
* @param string $new_status New comment status.
* @param string $old_status Old comment status.
* @param WP_Comment $comment Comment object.
*/
public static function schedule_comment_activity( $new_status, $old_status, $comment ) {
$comment = get_comment( $comment );
if ( ! $comment->user_id ) {
// Registered comment author
return;
}
if ( 'approved' === $new_status && 'approved' !== $old_status ) {
// Only federate replies from local actors
$activity_object = unserialize( \get_comment_meta( $comment->comment_ID, 'ap_object', true ) );
if ( empty( $activity_object ) ) {
\wp_schedule_single_event( \time(), 'activitypub_send_activity', array( $comment, 'Create' ) );
}
} elseif ( 'trash' === $new_status ) {
\wp_schedule_single_event( \time(), 'activitypub_send_activity', array( $comment, 'Delete' ) );
} elseif ( 'update' === $new_status ) {
\wp_schedule_single_event( \time(), 'activitypub_send_activity', array( $comment, 'Update' ) );
}
}
/**
* Update followers
*

View file

@ -2,7 +2,9 @@
namespace Activitypub;
use WP_Error;
use WP_Comment_Query;
use Activitypub\Http;
use Activitypub\Webfinger;
use Activitypub\Activity\Activity;
use Activitypub\Collection\Followers;
@ -166,6 +168,161 @@ function url_to_authorid( $url ) {
return 0;
}
/**
* Examine a comment ID and look up an existing comment it represents.
*
* @param string $id ActivityPub object ID (usually a URL) to check.
*
* @return WP_Comment, or undef if no comment could be found.
*/
function object_id_to_comment( $id ) {
$comment_query = new WP_Comment_Query(
array(
'meta_key' => 'source_id',
'meta_value' => $id,
)
);
if ( ! $comment_query->comments ) {
return;
}
if ( count( $comment_query->comments ) > 1 ) {
return;
}
return $comment_query->comments[0];
}
/**
* Examine an activity object and find the post that the specified URL field refers to.
*
* @param string $field_name The name of the URL field in the object to query.
*
* @return int Post ID, or null on failure.
*/
function object_to_post_id_by_field_name( $object, $field_name ) {
if ( ! isset( $object['object'][ $field_name ] ) ) {
return;
}
$result = \url_to_postid( $object['object'][ $field_name ] );
if ( $result > 0 ) {
return $result;
}
}
/**
* Verify if in_replyto_url is a local Post,
* (For backwards compatibility)
*
* @param string activitypub object id URI
* @return int post_id
*/
function url_to_postid( $in_replyto_url ) {
if ( ! empty( $in_replyto_url ) ) {
$tentative_postid = \url_to_postid( $in_replyto_url );
if ( is_null( $tentative_postid ) ) {
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) );
$query_args = array(
'type' => $post_types,
'meta_query' => array(
array(
'key' => 'activitypub_canonical_url',
'value' => $in_replyto_url,
),
),
);
$posts_query = new \WP_Query();
$posts = $posts_query->query( $query_args );
$found_post_ids = array();
if ( $posts ) {
foreach ( $posts as $post ) {
$found_post_ids[] = $post->comment_ID;
}
return $found_post_ids[0];
}
} else {
return $tentative_postid;
}
}
return null;
}
/**
* Verify if in_replyto_url is a local comment,
* Or if it is a previously received remote comment
* (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 ) ) {
return null;
}
//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'] && ! empty( $url_maybe_id['query'] ) ) {
//is local post or comment
\parse_str( $url_maybe_id['query'], $reply_query );
if ( isset( $reply_query['replytocom'] ) ) {
//is local comment
return $reply_query['replytocom'];
}
} else {
//is remote url
//verify if in_replyto_url corresponds to a previously received comment
$comment_args = array(
'type' => 'activitypub',
'meta_query' => array(
array(
'key' => 'source_url', // $object['object']['id']
'value' => $in_replyto_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;
}
/**
* Verify if url is a wp_ap_comment,
* Or if it is a previously received remote comment
*
* @return int comment_id
*/
function is_comment() {
$comment_id = get_query_var( 'replytocom', null );
if ( ! is_null( $comment_id ) ) {
$comment = \get_comment( $comment_id );
// Only return local origin comments
if ( $comment->user_id ) {
return $comment_id;
}
}
return null;
}
/**
* @param string $user_url
* @return string $webfinger
*/
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 = \wp_parse_url( $user_url, PHP_URL_HOST );
$webfinger = '@' . $user_name . '@' . $url_host;
return $webfinger;
}
/**
* Check for Tombstone Objects
*

View file

@ -337,45 +337,82 @@ class Inbox {
}
/**
* Handles "Create" requests
* Converts a new ActivityPub object to comment data suitable for creating a comment
*
* @param array $object The activity-object
* @param int $user_id The id of the local blog-user
* @param array $object The activity-object.
*
* @return array Comment data suitable for creating a comment.
*/
public static function handle_create( $object, $user_id ) {
$meta = get_remote_metadata_by_actor( $object['actor'] );
public static function convert_object_to_comment_data( $object, $user_id ) {
$object['user_id'] = $user_id;
if ( ! isset( $object['object']['inReplyTo'] ) ) {
return;
return false;
}
// check if Activity is public or not
if ( ! self::is_activity_public( $object ) ) {
// @todo maybe send email
return;
return false;
}
$comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
// save only replys and reactions
$id = $object['object']['id'];
// Only handle replies
if ( ! isset( $object['object']['inReplyTo'] ) ) {
return false;
}
$in_reply_to = $object['object']['inReplyTo'];
// Comment already exists
if ( \Activitypub\object_id_to_comment( $id ) ) {
return false;
}
$parent_comment = \Activitypub\object_id_to_comment( $in_reply_to );
// save only replies and reactions
$comment_post_id = \Activitypub\object_to_post_id_by_field_name( $object, 'context' );
if ( ! $comment_post_id ) {
$comment_post_id = \Activitypub\object_to_post_id_by_field_name( $object, 'inReplyTo' );
}
if ( ! $comment_post_id ) {
$comment_post_id = $parent_comment->comment_post_ID;
}
if ( ! $comment_post_id ) {
return false;
}
$commentdata = array(
return array(
'comment_post_ID' => $comment_post_id,
'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',
'comment_author_email' => '',
'comment_parent' => 0,
'comment_parent' => $parent_comment ? $parent_comment->comment_ID : 0,
'comment_meta' => array(
'ap_object' => \serialize( $object ),
'source_id' => \esc_url_raw( $id ),
'source_url' => \esc_url_raw( $object['object']['url'] ),
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
'protocol' => 'activitypub',
),
);
}
/**
* Handles "Create" requests
*
* @param array $object The activity-object
* @param int $user_id The id of the local blog-user
*/
public static function handle_create( $object, $user_id ) {
$commentdata = self::convert_object_to_comment_data( $object, $user_id );
if ( ! $commentdata ) {
return false;
}
// disable flood control
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
@ -401,6 +438,76 @@ class Inbox {
do_action( 'activitypub_handled_create', $object, $user_id, $state, $commentdata );
}
/**
* Handles "Update" requests
*
* @param array $object The activity-object
* @param int $user_id The id of the local blog-user
*/
public static function handle_update( $object, $user_id ) {
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
//Determine comment_ID
$object_comment_id = \Activitypub\url_to_commentid( \esc_url_raw( $object['object']['id'] ) );
if ( ! is_null( $object_comment_id ) ) {
//found a local comment id
$commentdata = \get_comment( $object_comment_id, ARRAY_A );
$commentdata['comment_author'] = \esc_attr( $meta['name'] ? $meta['name'] : $meta['preferredUsername'] );
$commentdata['comment_content'] = \wp_filter_kses( $object['object']['content'] );
$commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $meta['icon']['url'] );
$commentdata['comment_meta']['ap_published'] = \wp_date( 'Y-m-d H:i:s', strtotime( $object['object']['published'] ) );
$commentdata['comment_meta']['activitypub_last_modified'] = $object['object']['updated'];
$commentdata['comment_meta']['ap_object'] = \serialize( $object );
// disable flood control
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
// do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' );
$state = \wp_update_comment( $commentdata, true );
\remove_filter( 'pre_option_require_name_email', '__return_false' );
// re-add flood control
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
}
}
/**
* Handles "Delete" requests
*
* @param array $object The activity-object
* @param int $user_id The id of the local blog-user
*/
public static function handle_delete( $object, $user_id ) {
if ( ! isset( $object['object']['id'] ) ) {
return;
}
//Determine comment_ID
$object_comment_id = \Activitypub\url_to_commentid( \esc_url_raw( $object['object']['id'] ) );
if ( ! is_null( $object_comment_id ) ) {
//found a local comment id
$commentdata = \get_comment( $object_comment_id, ARRAY_A );
// disable flood control
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
// do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' );
// Should we trash or send back to moderation
$state = \wp_trash_comment( $commentdata['comment_ID'], true );
\remove_filter( 'pre_option_require_name_email', '__return_false' );
// re-add flood control
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
}
}
/**
* Extract recipient URLs from Activity object
*

View file

@ -0,0 +1,299 @@
<?php
namespace Activitypub\Transformer;
use WP_Comment;
use Activitypub\Collection\Users;
use Activitypub\Model\Blog_User;
use Activitypub\Activity\Base_Object;
use Activitypub\Hashtag;
use function Activitypub\esc_hashtag;
use function Activitypub\is_single_user;
use function Activitypub\get_rest_url_by_path;
/**
* WordPress Comment Transformer
*
* The Comment Transformer is responsible for transforming a WP_Comment object into different
* Object-Types.
*
* Currently supported are:
*
* - Activitypub\Activity\Base_Object
*/
class Comment {
/**
* The WP_Comment object.
*
* @var WP_Comment
*/
protected $wp_comment;
/**
* Static function to Transform a WP_Comment Object.
*
* This helps to chain the output of the Transformer.
*
* @param WP_Comment $wp_comment The WP_Comment object
*
* @return void
*/
public static function transform( WP_Comment $wp_comment ) {
return new static( $wp_comment );
}
/**
*
*
* @param WP_Comment $wp_comment
*/
public function __construct( WP_Comment $wp_comment ) {
$this->wp_comment = $wp_comment;
}
/**
* Transforms the WP_Comment object to an ActivityPub Object
*
* @see \Activitypub\Activity\Base_Object
*
* @return \Activitypub\Activity\Base_Object The ActivityPub Object
*/
public function to_object() {
$comment = $this->wp_comment;
$object = new Base_Object();
$object->set_id( $this->generate_id( $comment ) );
$object->set_url( \get_comment_link( $comment->ID ) );
$object->set_type( 'Note' );
$published = \strtotime( $comment->comment_date_gmt );
$object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
$updated = \get_comment_meta( $comment->comment_ID, 'activitypub_last_modified', true );
if ( $updated > $published ) {
$object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( $updated ) ) );
}
$object->set_attributed_to( $this->get_attributed_to() );
$object->set_in_reply_to( $this->get_in_reply_to() );
$object->set_content( $this->get_content() );
$object->set_content_map(
array(
$this->get_locale() => $this->get_content(),
)
);
$path = sprintf( 'users/%d/followers', intval( $comment->comment_author ) );
$object->set_to(
array(
'https://www.w3.org/ns/activitystreams#Public',
get_rest_url_by_path( $path ),
)
);
$object->set_cc( $this->get_cc() );
$object->set_tag( $this->get_tags() );
return $object;
}
/**
* Returns the User-URL of the Author of the Post.
*
* If `single_user` mode is enabled, the URL of the Blog-User is returned.
*
* @return string The User-URL.
*/
protected function get_attributed_to() {
if ( is_single_user() ) {
$user = new Blog_User();
return $user->get_url();
}
return Users::get_by_id( $this->wp_comment->user_id )->get_url();
}
/**
* Returns the content for the ActivityPub Item.
*
* The content will be generated based on the user settings.
*
* @return string The content.
*/
protected function get_content() {
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$comment = $this->wp_comment;
$content = $comment->comment_content;
$content = \wpautop( $content );
$content = \preg_replace( '/[\n\r\t]/', '', $content );
$content = \trim( $content );
$content = \apply_filters( 'the_content', $content, $comment );
return $content;
}
/**
* get_parent_uri (in reply to)
* takes a comment and returns
* @param WP_Comment activitypub object id URI
* @return int comment_id
*/
protected function get_in_reply_to() {
$comment = $this->wp_comment;
$parent_comment = \get_comment( $comment->comment_parent );
if ( $parent_comment ) {
//is parent remote?
$in_reply_to = $this->get_source_id( $parent_comment );
if ( ! $in_reply_to ) {
//local
$in_reply_to = $this->generate_id( $parent_comment );
}
} else {
$pretty_permalink = \get_post_meta( $comment->comment_post_ID, 'activitypub_canonical_url', true );
if ( $pretty_permalink ) {
$in_reply_to = $pretty_permalink;
} else {
$in_reply_to = \get_permalink( $comment->comment_post_ID );
}
}
return $in_reply_to;
}
/**
* @param $comment or $comment_id
* @return ActivityPub URI of comment
*
* AP Object ID must be unique
*
* https://www.w3.org/TR/activitypub/#obj-id
* https://github.com/tootsuite/mastodon/issues/13879
*/
protected function generate_id( $comment ) {
$comment = \get_comment( $comment );
$ap_comment_id = \add_query_arg(
array(
'p' => $comment->comment_post_ID,
'replytocom' => $comment->comment_ID,
),
\trailingslashit( site_url() )
);
return $ap_comment_id;
}
/**
* Checks a comment ID for a source_id, or source_url
*/
protected function get_source_id( $comment ) {
if ( $comment->user_id ) {
return null;
}
$source_id = \get_comment_meta( $comment->comment_ID, 'source_id', true );
if ( ! $source_id ) {
$source_url = \get_comment_meta( $comment->comment_ID, 'source_url', true );
if ( ! $source_url ) {
return null;
}
$response = \safe_remote_get( $source_url );
$body = \wp_remote_retrieve_body( $response );
$remote_status = \json_decode( $body, true );
if ( \is_wp_error( $remote_status )
|| ! isset( $remote_status['@context'] )
|| ! isset( $remote_status['object']['id'] ) ) {
// the original post may have been deleted, before we started processing deletes.
return null;
}
$source_id = $remote_status['object']['id'];
}
return $source_id;
}
protected function get_context() {
$comment = $this->wp_comment;
$pretty_permalink = \get_post_meta( $comment->comment_post_ID, 'activitypub_canonical_url', true );
if ( $pretty_permalink ) {
$context = $pretty_permalink;
} else {
$context = \get_permalink( $comment->comment_post_ID );
}
return $context;
}
/**
* Returns a list of Mentions, used in the Comment.
*
* @see https://docs.joinmastodon.org/spec/activitypub/#Mention
*
* @return array The list of Mentions.
*/
protected function get_cc() {
$cc = array();
$mentions = $this->get_mentions();
if ( $mentions ) {
foreach ( $mentions as $mention => $url ) {
$cc[] = $url;
}
}
return $cc;
}
/**
* Returns a list of Tags, used in the Comment.
*
* This includes Hash-Tags and Mentions.
*
* @return array The list of Tags.
*/
protected function get_tags() {
$tags = array();
$mentions = $this->get_mentions();
if ( $mentions ) {
foreach ( $mentions as $mention => $url ) {
$tag = array(
'type' => 'Mention',
'href' => \esc_url( $url ),
'name' => \esc_html( $mention ),
);
$tags[] = $tag;
}
}
return $tags;
}
/**
* Helper function to get the @-Mentions from the comment content.
*
* @return array The list of @-Mentions.
*/
protected function get_mentions() {
return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_comment->comment_content, $this->wp_comment );
}
/**
* Returns the locale of the post.
*
* @return string The locale of the post.
*/
public function get_locale() {
$comment_id = $this->wp_comment->ID;
$lang = \strtolower( \strtok( \get_locale(), '_-' ) );
/**
* Filter the locale of the comment.
*
* @param string $lang The locale of the comment.
* @param int $comment_id The comment ID.
* @param WP_Post $post The comment object.
*
* @return string The filtered locale of the comment.
*/
return apply_filters( 'activitypub_comment_locale', $lang, $comment_id, $this->wp_comment );
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Activitypub\Transformer;
use Activitypub\Transformer\Post;
use Activitypub\Transformer\Comment;
/**
* Transformer Factory
*/
class Factory {
public static function get_transformer( $object ) {
switch ( get_class( $object ) ) {
case 'WP_Post':
return new Post( $object );
case 'WP_Comment':
return new Comment( $object );
default:
return apply_filters( 'activitypub_transformer', null, $object, get_class( $object ) );
}
}
}

View file

@ -580,4 +580,63 @@ class Post {
protected function get_mentions() {
return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_post->post_content, $this->wp_post );
}
/**
* Get deleted datetime
*/
public function get_deleted() {
$post = $this->wp_post;
$deleted = null;
if ( 'trash' === $post->post_status ) {
$this->deleted = \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_modified_gmt ) );
}
return $deleted;
}
public function get_replies() {
$replies = null;
$post = $this->wp_post;
if ( $post->comment_count > 0 ) {
$args = array(
'post_id' => $post->ID,
'hierarchical' => false,
'status' => 'approve',
);
$comments = \get_comments( $args );
$items = array();
foreach ( $comments as $comment ) {
// include self replies
if ( $post->post_author === $comment->user_id ) {
$comment_url = \add_query_arg( //
array(
'p' => $post->ID,
'replytocom' => $comment->comment_ID,
),
trailingslashit( site_url() )
);
$items[] = $comment_url;
} else {
$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 );
}
}
}
$replies = array(
'type' => 'Collection',
'id' => \add_query_arg( array( 'replies' => '' ), $this->get_id() ),
'first' => array(
'type' => 'CollectionPage',
'partOf' => \add_query_arg( array( 'replies' => '' ), $this->get_id() ),
'items' => $items,
),
);
}
return $replies;
}
}

View file

@ -0,0 +1,36 @@
<?php
$comment = \get_comment( \get_query_var( 'replytocom' ) ); // phpcs:ignore
$object = new \Activitypub\Transformer\Comment( $comment );
$json = \array_merge( array( '@context' => \Activitypub\get_context() ), $object->to_object()->to_array() );
// filter output
$json = \apply_filters( 'activitypub_json_comment_array', $json );
/*
* Action triggerd prior to the ActivityPub profile being created and sent to the client
*/
\do_action( 'activitypub_json_comment_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_comment_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_comment_comment' );

View file

@ -0,0 +1,66 @@
<?php
class Test_Inbox extends WP_UnitTestCase {
public $post_permalink;
public $user_url;
public function set_up() {
$authordata = \get_userdata( 1 );
$this->user_url = $authordata->user_url;
$post = \wp_insert_post(
array(
'post_author' => 1,
'post_content' => 'test',
)
);
$this->post_permalink = \get_permalink( $post );
\add_filter( 'pre_get_remote_metadata_by_actor', array( '\Test_Inbox', 'get_remote_metadata_by_actor' ), 10, 2 );
}
public static function get_remote_metadata_by_actor( $value, $actor ) {
return array(
'name' => 'Example User',
'icon' => array(
'url' => 'https://example.com/icon',
),
);
}
public function test_convert_object_to_comment_data_basic() {
$object = array(
'actor' => $this->user_url,
'to' => [ $this->user_url ],
'cc' => [ 'https://www.w3.org/ns/activitystreams#Public' ],
'object' => array(
'id' => '123',
'url' => 'https://example.com/example',
'inReplyTo' => $this->post_permalink,
'content' => 'example',
),
);
$converted = \Activitypub\Rest\Inbox::convert_object_to_comment_data( $object, 1 );
$this->assertGreaterThan( 1, $converted['comment_post_ID'] );
$this->assertEquals( $converted['comment_author'], 'Example User' );
$this->assertEquals( $converted['comment_author_url'], 'http://example.org' );
$this->assertEquals( $converted['comment_content'], 'example' );
$this->assertEquals( $converted['comment_type'], 'comment' );
$this->assertEquals( $converted['comment_author_email'], '' );
$this->assertEquals( $converted['comment_parent'], 0 );
$this->assertArrayHasKey( 'comment_meta', $converted );
$this->assertEquals( $converted['comment_meta']['source_id'], 'http://123' );
$this->assertEquals( $converted['comment_meta']['source_url'], 'https://example.com/example' );
$this->assertEquals( $converted['comment_meta']['avatar_url'], 'https://example.com/icon' );
$this->assertEquals( $converted['comment_meta']['protocol'], 'activitypub' );
}
public function test_convert_object_to_comment_data_non_public_rejected() {
$object = array(
'to' => array( 'https://example.com/profile/test' ),
'cc' => array(),
);
$converted = \Activitypub\Rest\Inbox::convert_object_to_comment_data( $object, 1 );
$this->assertFalse( $converted );
}
}