Compare commits
167 commits
main
...
add/commen
Author | SHA1 | Date | |
---|---|---|---|
|
e9ea9db6a3 | ||
|
7d525a8efe | ||
|
8a5e83b619 | ||
|
1e5c7675cd | ||
|
5b440b3819 | ||
|
84e394f9df | ||
|
ebe9af4497 | ||
|
be2d6683bb | ||
|
24315a41b6 | ||
|
d12708e746 | ||
|
1353c89b38 | ||
|
2d09cf5b45 | ||
|
15555bdd31 | ||
|
3c089a6549 | ||
|
406d03345e | ||
|
921829f1b4 | ||
|
9da43a9d71 | ||
|
2335fb1b47 | ||
|
e04816a12b | ||
|
06be55b61a | ||
|
9d1ba79788 | ||
|
5301cdaed7 | ||
|
e6473db4f8 | ||
|
375886b3de | ||
|
9af54707b9 | ||
|
8a0e02b39f | ||
|
e7c065d173 | ||
|
c7da88a35c | ||
|
588c5698de | ||
|
e8736c15a9 | ||
|
8f03fdeaf8 | ||
|
a18740c1ef | ||
|
69a0a90b10 | ||
|
1a58b69b80 | ||
|
f92524b384 | ||
|
77e611cf2c | ||
|
e57932b099 | ||
|
0185e38013 | ||
|
b9e49d42c3 | ||
|
69195f8b3a | ||
|
50f7ae2a04 | ||
|
a9c1862369 | ||
|
b07e756927 | ||
|
63a8502205 | ||
|
110eb409d4 | ||
|
92fd280133 | ||
|
a78a581b41 | ||
|
f018432033 | ||
|
734235b500 | ||
|
bf5cccf685 | ||
|
f44298f503 | ||
|
641d250a06 | ||
|
7339a6419d | ||
|
d67556ca33 | ||
|
8495fda263 | ||
|
133c16dc3e | ||
|
f808ff33cb | ||
|
da2495e88f | ||
|
43452e4443 | ||
|
c9c155a858 | ||
|
af6dd43241 | ||
|
e4c991393d | ||
|
1927587e95 | ||
|
3017e10e06 | ||
|
86c8148cde | ||
|
9f1b65b7ef | ||
|
0ab95add02 | ||
|
6f55010df2 | ||
|
1fe5959960 | ||
|
d07bb1f808 | ||
|
a058ec0569 | ||
|
1ac6882f3a | ||
|
116070e23c | ||
|
30129adac5 | ||
|
e46137a970 | ||
|
0525645f7c | ||
|
33429cfb83 | ||
|
7e6c112679 | ||
|
2e72e93cbd | ||
|
627e5a9c0c | ||
|
9cab6248e9 | ||
|
f7fad1b118 | ||
|
44d05351eb | ||
|
f943de1e79 | ||
|
aca5e273fe | ||
|
c653575594 | ||
|
a159d70658 | ||
|
80901b5b65 | ||
|
f9c0edc681 | ||
|
22e0ddc134 | ||
|
a9a5b112b0 | ||
|
85ca37aa1d | ||
|
7fa58cf26c | ||
|
8db9be5c2e | ||
|
6a906e5fe2 | ||
|
f1f3c3165d | ||
|
45b6e63f32 | ||
|
6b15d8d158 | ||
|
733bc88faf | ||
|
8f6ad230ce | ||
|
ae54805cf0 | ||
|
2ca6bdc487 | ||
|
f1dfd52329 | ||
|
093bc3c88b | ||
|
b04538a2ab | ||
|
e48986dd99 | ||
|
a6657edd4a | ||
|
0f356acee8 | ||
|
0b881bb9fd | ||
|
3f92bcc41b | ||
|
fe7def2c84 | ||
|
5dbf365c58 | ||
|
b1f64f6828 | ||
|
d7c9e10c21 | ||
|
18a8752c6a | ||
|
8cce944edd | ||
|
1ef6fc21c1 | ||
|
f87dbd87be | ||
|
de74f1d70b | ||
|
c801d072c8 | ||
|
852634360b | ||
|
055c22e9c3 | ||
|
8707387ab8 | ||
|
ecbe724b82 | ||
|
2d0e962263 | ||
|
6a69a40295 | ||
|
63bf62402d | ||
|
aa1b54462e | ||
|
be9b74478d | ||
4e3069d931 | |||
308df84c28 | |||
a5a6967b48 | |||
6fe621bf50 | |||
30a37dab6e | |||
7c24e994ec | |||
cbc989fe83 | |||
db84a5257a | |||
b4e79e1988 | |||
|
a4e8cc7e14 | ||
|
6af56cf82e | ||
|
36bef694b6 | ||
|
3db5fb2626 | ||
|
5d8e3a9ba9 | ||
|
e5080f8693 | ||
2d50b44d13 | |||
|
7fe1ce4888 | ||
|
8e1c9ff6bb | ||
|
51643142aa | ||
|
73e6be9782 | ||
|
8938c67073 | ||
|
163f2ed013 | ||
6772d300b4 | |||
a88eea6c1c | |||
|
395187a717 | ||
|
a41f84e88f | ||
|
dc5ef86887 | ||
|
0462e9d7b0 | ||
|
4c723d1e28 | ||
|
de98e07019 | ||
|
4a4589ae1f | ||
e0730a62be | |||
8733957963 | |||
338200b386 | |||
d452ba8585 | |||
8e98fffab6 | |||
|
146321102d | ||
|
17c386cb9d |
14 changed files with 943 additions and 37 deletions
|
@ -72,6 +72,7 @@ function plugin_init() {
|
||||||
\add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) );
|
\add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) );
|
||||||
\add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) );
|
\add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) );
|
||||||
\add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) );
|
\add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) );
|
||||||
|
\add_action( 'init', array( __NAMESPACE__ . '\Comments', 'init' ) );
|
||||||
|
|
||||||
if ( site_supports_blocks() ) {
|
if ( site_supports_blocks() ) {
|
||||||
\add_action( 'init', array( __NAMESPACE__ . '\Blocks', 'init' ) );
|
\add_action( 'init', array( __NAMESPACE__ . '\Blocks', 'init' ) );
|
||||||
|
|
|
@ -11,10 +11,11 @@ jQuery( function( $ ) {
|
||||||
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
|
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
$(document).on( 'wp-plugin-install-success', function( event, response ) {
|
$(document).on( 'wp-plugin-install-success', function( event, response ) {
|
||||||
setTimeout( function() {
|
setTimeout( function() {
|
||||||
$( '.activate-now' ).removeClass( 'thickbox open-plugin-details-modal' );
|
$( '.activate-now' ).removeClass( 'thickbox open-plugin-details-modal' );
|
||||||
}, 1200 );
|
}, 1200 );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
namespace Activitypub;
|
namespace Activitypub;
|
||||||
|
|
||||||
use WP_Post;
|
use WP_Post;
|
||||||
|
use WP_Comment;
|
||||||
use Activitypub\Activity\Activity;
|
use Activitypub\Activity\Activity;
|
||||||
use Activitypub\Collection\Users;
|
use Activitypub\Collection\Users;
|
||||||
use Activitypub\Collection\Followers;
|
use Activitypub\Collection\Followers;
|
||||||
|
use Activitypub\Transformer\Factory;
|
||||||
use Activitypub\Transformer\Post;
|
use Activitypub\Transformer\Post;
|
||||||
|
use Activitypub\Transformer\Comment;
|
||||||
|
|
||||||
use function Activitypub\is_single_user;
|
use function Activitypub\is_single_user;
|
||||||
use function Activitypub\is_user_disabled;
|
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.
|
* Send Activities to followers and mentioned users or `Announce` (boost) a blog post.
|
||||||
*
|
*
|
||||||
* @param WP_Post $wp_post The ActivityPub Post.
|
* @param mixed $wp_object The ActivityPub Post.
|
||||||
* @param string $type The Activity-Type.
|
* @param string $type The Activity-Type.
|
||||||
*
|
*
|
||||||
* @return void
|
* @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
|
// check if a migration is needed before sending new posts
|
||||||
Migration::maybe_migrate();
|
Migration::maybe_migrate();
|
||||||
|
|
||||||
|
@ -43,35 +46,35 @@ class Activity_Dispatcher {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$wp_post->post_author = Users::BLOG_USER_ID;
|
$wp_object->post_author = Users::BLOG_USER_ID;
|
||||||
|
|
||||||
if ( is_single_user() ) {
|
if ( is_single_user() ) {
|
||||||
self::send_activity( $wp_post, $type );
|
self::send_activity( $wp_object, $type );
|
||||||
} else {
|
} else {
|
||||||
self::send_announce( $wp_post, $type );
|
self::send_announce( $wp_object, $type );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send Activities to followers and mentioned users.
|
* Send Activities to followers and mentioned users.
|
||||||
*
|
*
|
||||||
* @param WP_Post $wp_post The ActivityPub Post.
|
* @param mixed $wp_object The ActivityPub Post.
|
||||||
* @param string $type The Activity-Type.
|
* @param string $type The Activity-Type.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function send_activity( WP_Post $wp_post, $type ) {
|
public static function send_activity( $wp_object, $type ) {
|
||||||
if ( is_user_disabled( $wp_post->post_author ) ) {
|
if ( is_user_disabled( $wp_object->post_author ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$object = Post::transform( $wp_post )->to_object();
|
$object = Factory::get_transformer( $wp_object )->to_object();
|
||||||
|
|
||||||
$activity = new Activity();
|
$activity = new Activity();
|
||||||
$activity->set_type( $type );
|
$activity->set_type( $type );
|
||||||
$activity->set_object( $object );
|
$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() );
|
$mentioned_inboxes = Mention::get_inboxes( $activity->get_cc() );
|
||||||
|
|
||||||
$inboxes = array_merge( $follower_inboxes, $mentioned_inboxes );
|
$inboxes = array_merge( $follower_inboxes, $mentioned_inboxes );
|
||||||
|
@ -80,19 +83,19 @@ class Activity_Dispatcher {
|
||||||
$json = $activity->to_json();
|
$json = $activity->to_json();
|
||||||
|
|
||||||
foreach ( $inboxes as $inbox ) {
|
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.
|
* Send Announces to followers and mentioned users.
|
||||||
*
|
*
|
||||||
* @param WP_Post $wp_post The ActivityPub Post.
|
* @param mixed $wp_object The ActivityPub Post.
|
||||||
* @param string $type The Activity-Type.
|
* @param string $type The Activity-Type.
|
||||||
*
|
*
|
||||||
* @return void
|
* @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 ) ) {
|
if ( ! in_array( $type, array( 'Create', 'Update' ), true ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +104,7 @@ class Activity_Dispatcher {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$object = Post::transform( $wp_post )->to_object();
|
$object = Factory::get_transformer( $wp_object )->to_object();
|
||||||
|
|
||||||
$activity = new Activity();
|
$activity = new Activity();
|
||||||
$activity->set_type( 'Announce' );
|
$activity->set_type( 'Announce' );
|
||||||
|
@ -110,7 +113,7 @@ class Activity_Dispatcher {
|
||||||
// send only the id
|
// send only the id
|
||||||
$activity->set_object( $object->get_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() );
|
$mentioned_inboxes = Mention::get_inboxes( $activity->get_cc() );
|
||||||
|
|
||||||
$inboxes = array_merge( $follower_inboxes, $mentioned_inboxes );
|
$inboxes = array_merge( $follower_inboxes, $mentioned_inboxes );
|
||||||
|
@ -119,7 +122,7 @@ class Activity_Dispatcher {
|
||||||
$json = $activity->to_json();
|
$json = $activity->to_json();
|
||||||
|
|
||||||
foreach ( $inboxes as $inbox ) {
|
foreach ( $inboxes as $inbox ) {
|
||||||
safe_remote_post( $inbox, $json, $wp_post->post_author );
|
safe_remote_post( $inbox, $json, $wp_object->post_author );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ namespace Activitypub;
|
||||||
use Activitypub\Signature;
|
use Activitypub\Signature;
|
||||||
use Activitypub\Collection\Users;
|
use Activitypub\Collection\Users;
|
||||||
|
|
||||||
|
use function Activitypub\is_comment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActivityPub Class
|
* ActivityPub Class
|
||||||
*
|
*
|
||||||
|
@ -92,6 +94,8 @@ class Activitypub {
|
||||||
|
|
||||||
if ( \is_author() ) {
|
if ( \is_author() ) {
|
||||||
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php';
|
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php';
|
||||||
|
} elseif ( is_comment() ) {
|
||||||
|
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/comment-json.php';
|
||||||
} elseif ( \is_singular() ) {
|
} elseif ( \is_singular() ) {
|
||||||
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-json.php';
|
$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-json.php';
|
||||||
} elseif ( \is_home() ) {
|
} elseif ( \is_home() ) {
|
||||||
|
@ -114,6 +118,7 @@ class Activitypub {
|
||||||
*/
|
*/
|
||||||
public static function add_query_vars( $vars ) {
|
public static function add_query_vars( $vars ) {
|
||||||
$vars[] = 'activitypub';
|
$vars[] = 'activitypub';
|
||||||
|
$vars[] = 'replytocom';
|
||||||
|
|
||||||
return $vars;
|
return $vars;
|
||||||
}
|
}
|
||||||
|
@ -189,9 +194,11 @@ class Activitypub {
|
||||||
* @return string $url
|
* @return string $url
|
||||||
*/
|
*/
|
||||||
public static function remote_comment_link( $comment_link, $comment ) {
|
public static function remote_comment_link( $comment_link, $comment ) {
|
||||||
$remote_comment_link = get_comment_meta( $comment->comment_ID, 'source_url', true );
|
if ( ! is_admin() ) {
|
||||||
if ( $remote_comment_link ) {
|
$remote_comment_link = get_comment_meta( $comment->comment_ID, 'source_url', true );
|
||||||
$comment_link = esc_url( $remote_comment_link );
|
if ( $remote_comment_link ) {
|
||||||
|
$comment_link = esc_url( $remote_comment_link );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $comment_link;
|
return $comment_link;
|
||||||
}
|
}
|
||||||
|
|
118
includes/class-comments.php
Normal file
118
includes/class-comments.php
Normal 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' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,7 +63,7 @@ class Hashtag {
|
||||||
$tag = strtolower( $m[2] );
|
$tag = strtolower( $m[2] );
|
||||||
if ( '/' === $m[1] ) {
|
if ( '/' === $m[1] ) {
|
||||||
// Closing tag.
|
// 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.
|
// We can only remove the tag from the stack if it is in the stack.
|
||||||
if ( false !== $i ) {
|
if ( false !== $i ) {
|
||||||
$tag_stack = array_slice( $tag_stack, 0, $i );
|
$tag_stack = array_slice( $tag_stack, 0, $i );
|
||||||
|
|
|
@ -17,6 +17,7 @@ class Scheduler {
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
\add_action( 'transition_post_status', array( self::class, 'schedule_post_activity' ), 33, 3 );
|
\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_update_followers', array( self::class, 'update_followers' ) );
|
||||||
\add_action( 'activitypub_cleanup_followers', array( self::class, 'cleanup_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
|
* Update followers
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
namespace Activitypub;
|
namespace Activitypub;
|
||||||
|
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
|
use WP_Comment_Query;
|
||||||
use Activitypub\Http;
|
use Activitypub\Http;
|
||||||
|
use Activitypub\Webfinger;
|
||||||
use Activitypub\Activity\Activity;
|
use Activitypub\Activity\Activity;
|
||||||
use Activitypub\Collection\Followers;
|
use Activitypub\Collection\Followers;
|
||||||
|
|
||||||
|
@ -166,6 +168,161 @@ function url_to_authorid( $url ) {
|
||||||
return 0;
|
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
|
* Check for Tombstone Objects
|
||||||
*
|
*
|
||||||
|
|
|
@ -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 array $object The activity-object.
|
||||||
* @param int $user_id The id of the local blog-user
|
*
|
||||||
|
* @return array Comment data suitable for creating a comment.
|
||||||
*/
|
*/
|
||||||
public static function handle_create( $object, $user_id ) {
|
public static function convert_object_to_comment_data( $object, $user_id ) {
|
||||||
$meta = get_remote_metadata_by_actor( $object['actor'] );
|
$object['user_id'] = $user_id;
|
||||||
|
|
||||||
if ( ! isset( $object['object']['inReplyTo'] ) ) {
|
if ( ! isset( $object['object']['inReplyTo'] ) ) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if Activity is public or not
|
// check if Activity is public or not
|
||||||
if ( ! self::is_activity_public( $object ) ) {
|
if ( ! self::is_activity_public( $object ) ) {
|
||||||
// @todo maybe send email
|
// @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 ) {
|
if ( ! $comment_post_id ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$commentdata = array(
|
return array(
|
||||||
'comment_post_ID' => $comment_post_id,
|
'comment_post_ID' => $comment_post_id,
|
||||||
'comment_author' => \esc_attr( $meta['name'] ),
|
'comment_author' => \esc_attr( $meta['name'] ),
|
||||||
'comment_author_url' => \esc_url_raw( $object['actor'] ),
|
'comment_author_url' => \esc_url_raw( $object['actor'] ),
|
||||||
'comment_content' => \wp_filter_kses( $object['object']['content'] ),
|
'comment_content' => \wp_filter_kses( $object['object']['content'] ),
|
||||||
'comment_type' => 'comment',
|
'comment_type' => 'comment',
|
||||||
'comment_author_email' => '',
|
'comment_author_email' => '',
|
||||||
'comment_parent' => 0,
|
'comment_parent' => $parent_comment ? $parent_comment->comment_ID : 0,
|
||||||
'comment_meta' => array(
|
'comment_meta' => array(
|
||||||
|
'ap_object' => \serialize( $object ),
|
||||||
|
'source_id' => \esc_url_raw( $id ),
|
||||||
'source_url' => \esc_url_raw( $object['object']['url'] ),
|
'source_url' => \esc_url_raw( $object['object']['url'] ),
|
||||||
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
|
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
|
||||||
'protocol' => 'activitypub',
|
'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
|
// disable flood control
|
||||||
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
\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 );
|
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
|
* Extract recipient URLs from Activity object
|
||||||
*
|
*
|
||||||
|
|
299
includes/transformer/class-comment.php
Normal file
299
includes/transformer/class-comment.php
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
21
includes/transformer/class-factory.php
Normal file
21
includes/transformer/class-factory.php
Normal 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 ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -580,4 +580,63 @@ class Post {
|
||||||
protected function get_mentions() {
|
protected function get_mentions() {
|
||||||
return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_post->post_content, $this->wp_post );
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
36
templates/comment-json.php
Normal file
36
templates/comment-json.php
Normal 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' );
|
66
tests/test-class-inbox.php
Normal file
66
tests/test-class-inbox.php
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue