mirror of
https://github.com/Automattic/wordpress-activitypub
synced 2024-10-18 14:23:31 +00:00
Add like handler (#804)
* Update class-comment.php * Initial attempt * Register like handler * Add support for undoing likes * add support for announce and copied basic comment_type support handling from the Webmention plugin * fix merge issues * remove C&P issues * add missing phpdoc * Disable Announces and Likes by default. * set ACTIVITYPUB_DISABLE_REACTIONS to false * refactorings * fix escaping * add default object type * deduplicate code --------- Co-authored-by: Matthias Pfefferle <pfefferle@users.noreply.github.com>
This commit is contained in:
parent
7806285d88
commit
dabff80cc2
12 changed files with 531 additions and 95 deletions
|
@ -37,6 +37,8 @@ require_once __DIR__ . '/includes/functions.php';
|
||||||
\defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) || \define( 'ACTIVITYPUB_AUTHORIZED_FETCH', false );
|
\defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) || \define( 'ACTIVITYPUB_AUTHORIZED_FETCH', false );
|
||||||
\defined( 'ACTIVITYPUB_DISABLE_REWRITES' ) || \define( 'ACTIVITYPUB_DISABLE_REWRITES', false );
|
\defined( 'ACTIVITYPUB_DISABLE_REWRITES' ) || \define( 'ACTIVITYPUB_DISABLE_REWRITES', false );
|
||||||
\defined( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS' ) || \define( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS', false );
|
\defined( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS' ) || \define( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS', false );
|
||||||
|
// Disable reactions like `Like` and `Accounce` by default
|
||||||
|
\defined( 'ACTIVITYPUB_DISABLE_REACTIONS' ) || \define( 'ACTIVITYPUB_DISABLE_REACTIONS', true );
|
||||||
\defined( 'ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS' ) || \define( 'ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS', false );
|
\defined( 'ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS' ) || \define( 'ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS', false );
|
||||||
\defined( 'ACTIVITYPUB_SHARED_INBOX_FEATURE' ) || \define( 'ACTIVITYPUB_SHARED_INBOX_FEATURE', false );
|
\defined( 'ACTIVITYPUB_SHARED_INBOX_FEATURE' ) || \define( 'ACTIVITYPUB_SHARED_INBOX_FEATURE', false );
|
||||||
\defined( 'ACTIVITYPUB_SEND_VARY_HEADER' ) || \define( 'ACTIVITYPUB_SEND_VARY_HEADER', false );
|
\defined( 'ACTIVITYPUB_SEND_VARY_HEADER' ) || \define( 'ACTIVITYPUB_SEND_VARY_HEADER', false );
|
||||||
|
|
|
@ -19,6 +19,8 @@ class Comment {
|
||||||
* Initialize the class, registering WordPress hooks
|
* Initialize the class, registering WordPress hooks
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
|
self::register_comment_types();
|
||||||
|
|
||||||
\add_filter( 'comment_reply_link', array( self::class, 'comment_reply_link' ), 10, 3 );
|
\add_filter( 'comment_reply_link', array( self::class, 'comment_reply_link' ), 10, 3 );
|
||||||
\add_filter( 'comment_class', array( self::class, 'comment_class' ), 10, 3 );
|
\add_filter( 'comment_class', array( self::class, 'comment_class' ), 10, 3 );
|
||||||
\add_filter( 'get_comment_link', array( self::class, 'remote_comment_link' ), 11, 3 );
|
\add_filter( 'get_comment_link', array( self::class, 'remote_comment_link' ), 11, 3 );
|
||||||
|
@ -463,4 +465,115 @@ class Comment {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the registered custom comment types.
|
||||||
|
*
|
||||||
|
* @return array The registered custom comment types
|
||||||
|
*/
|
||||||
|
public static function get_comment_types() {
|
||||||
|
global $activitypub_comment_types;
|
||||||
|
|
||||||
|
return $activitypub_comment_types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a registered comment type
|
||||||
|
*
|
||||||
|
* @param string $slug The name of the type
|
||||||
|
* @return boolean True if registered.
|
||||||
|
*/
|
||||||
|
public static function is_registered_comment_type( $slug ) {
|
||||||
|
$slug = strtolower( $slug );
|
||||||
|
$slug = sanitize_key( $slug );
|
||||||
|
|
||||||
|
return in_array( $slug, array_keys( self::get_comment_types() ), true );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the registered custom comment types names.
|
||||||
|
*
|
||||||
|
* @return array The registered custom comment type names
|
||||||
|
*/
|
||||||
|
public static function get_comment_type_names() {
|
||||||
|
return array_values( wp_list_pluck( self::get_comment_types(), 'type' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a comment type
|
||||||
|
*
|
||||||
|
* @param string $type The comment type
|
||||||
|
*
|
||||||
|
* @return array The comment type
|
||||||
|
*/
|
||||||
|
public static function get_comment_type( $type ) {
|
||||||
|
$type = strtolower( $type );
|
||||||
|
$type = sanitize_key( $type );
|
||||||
|
$types = self::get_comment_types();
|
||||||
|
|
||||||
|
if ( in_array( $type, array_keys( $types ), true ) ) {
|
||||||
|
$type_array = $types[ $type ];
|
||||||
|
} else {
|
||||||
|
$type_array = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply_filters( "activitypub_comment_type_{$type}", $type_array );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a comment type attribute
|
||||||
|
*
|
||||||
|
* @param string $type The comment type
|
||||||
|
* @param string $attr The attribute to get
|
||||||
|
*
|
||||||
|
* @return mixed The value of the attribute
|
||||||
|
*/
|
||||||
|
public static function get_comment_type_attr( $type, $attr ) {
|
||||||
|
$type_array = self::get_comment_type( $type );
|
||||||
|
|
||||||
|
if ( $type_array && isset( $type_array[ $attr ] ) ) {
|
||||||
|
$value = $type_array[ $attr ];
|
||||||
|
} else {
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply_filters( "activitypub_comment_type_{$attr}", $value, $type );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the comment types used by the ActivityPub plugin
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register_comment_types() {
|
||||||
|
register_comment_type(
|
||||||
|
'announce',
|
||||||
|
array(
|
||||||
|
'label' => __( 'Reposts', 'activitypub' ),
|
||||||
|
'singular' => __( 'Repost', 'activitypub' ),
|
||||||
|
'description' => __( 'A repost on the indieweb is a post that is purely a 100% re-publication of another (typically someone else\'s) post.', 'activitypub' ),
|
||||||
|
'icon' => '♻️',
|
||||||
|
'class' => 'p-repost',
|
||||||
|
'type' => 'repost',
|
||||||
|
// translators: %1$s username, %2$s opject format (post, audio, ...), %3$s URL, %4$s domain
|
||||||
|
'excerpt' => __( '… reposted this!', 'activitypub' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
register_comment_type(
|
||||||
|
'like',
|
||||||
|
array(
|
||||||
|
'label' => __( 'Likes', 'activitypub' ),
|
||||||
|
'singular' => __( 'Like', 'activitypub' ),
|
||||||
|
'description' => __( 'A like is a popular webaction button and in some cases post type on various silos such as Facebook and Instagram.', 'activitypub' ),
|
||||||
|
'icon' => '👍',
|
||||||
|
'class' => 'p-like',
|
||||||
|
'type' => 'like',
|
||||||
|
// translators: %1$s username, %2$s opject format (post, audio, ...), %3$s URL, %4$s domain
|
||||||
|
'excerpt' => __( '… liked this!', 'activitypub' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use Activitypub\Handler\Announce;
|
||||||
use Activitypub\Handler\Create;
|
use Activitypub\Handler\Create;
|
||||||
use Activitypub\Handler\Delete;
|
use Activitypub\Handler\Delete;
|
||||||
use Activitypub\Handler\Follow;
|
use Activitypub\Handler\Follow;
|
||||||
|
use Activitypub\Handler\Like;
|
||||||
use Activitypub\Handler\Undo;
|
use Activitypub\Handler\Undo;
|
||||||
use Activitypub\Handler\Update;
|
use Activitypub\Handler\Update;
|
||||||
|
|
||||||
|
@ -30,6 +31,10 @@ class Handler {
|
||||||
Undo::init();
|
Undo::init();
|
||||||
Update::init();
|
Update::init();
|
||||||
|
|
||||||
|
if ( ! ACTIVITYPUB_DISABLE_REACTIONS ) {
|
||||||
|
Like::init();
|
||||||
|
}
|
||||||
|
|
||||||
do_action( 'activitypub_register_handlers' );
|
do_action( 'activitypub_register_handlers' );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ namespace Activitypub\Collection;
|
||||||
|
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
use WP_Comment_Query;
|
use WP_Comment_Query;
|
||||||
|
use Activitypub\Comment;
|
||||||
|
|
||||||
use function Activitypub\object_to_uri;
|
use function Activitypub\object_to_uri;
|
||||||
use function Activitypub\url_to_commentid;
|
use function Activitypub\url_to_commentid;
|
||||||
|
@ -13,6 +14,9 @@ use function Activitypub\get_remote_metadata_by_actor;
|
||||||
* ActivityPub Interactions Collection
|
* ActivityPub Interactions Collection
|
||||||
*/
|
*/
|
||||||
class Interactions {
|
class Interactions {
|
||||||
|
const INSERT = 'insert';
|
||||||
|
const UPDATE = 'update';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a comment to a post
|
* Add a comment to a post
|
||||||
*
|
*
|
||||||
|
@ -21,20 +25,15 @@ class Interactions {
|
||||||
* @return array|false The commentdata or false on failure
|
* @return array|false The commentdata or false on failure
|
||||||
*/
|
*/
|
||||||
public static function add_comment( $activity ) {
|
public static function add_comment( $activity ) {
|
||||||
if (
|
$commentdata = self::activity_to_comment( $activity );
|
||||||
! isset( $activity['object'] ) ||
|
|
||||||
! isset( $activity['object']['id'] )
|
if ( ! $commentdata || ! isset( $activity['object']['inReplyTo'] ) ) {
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! isset( $activity['object']['inReplyTo'] ) ) {
|
$in_reply_to = \esc_url_raw( $activity['object']['inReplyTo'] );
|
||||||
return false;
|
$comment_post_id = \url_to_postid( $in_reply_to );
|
||||||
}
|
$parent_comment_id = url_to_commentid( $in_reply_to );
|
||||||
|
|
||||||
$in_reply_to = \esc_url_raw( $activity['object']['inReplyTo'] );
|
|
||||||
$comment_post_id = \url_to_postid( $in_reply_to );
|
|
||||||
$parent_comment_id = url_to_commentid( $in_reply_to );
|
|
||||||
|
|
||||||
// save only replys and reactions
|
// save only replys and reactions
|
||||||
if ( ! $comment_post_id && $parent_comment_id ) {
|
if ( ! $comment_post_id && $parent_comment_id ) {
|
||||||
|
@ -47,58 +46,10 @@ class Interactions {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$actor = object_to_uri( $activity['actor'] );
|
$commentdata['comment_post_ID'] = $comment_post_id;
|
||||||
$meta = get_remote_metadata_by_actor( $actor );
|
$commentdata['comment_parent'] = $parent_comment_id ? $parent_comment_id : 0;
|
||||||
|
|
||||||
if ( ! $meta || \is_wp_error( $meta ) ) {
|
return self::persist( $commentdata, self::INSERT );
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$url = object_to_uri( $meta['url'] );
|
|
||||||
|
|
||||||
$commentdata = array(
|
|
||||||
'comment_post_ID' => $comment_post_id,
|
|
||||||
'comment_author' => isset( $meta['name'] ) ? \esc_attr( $meta['name'] ) : \esc_attr( $meta['preferredUsername'] ),
|
|
||||||
'comment_author_url' => \esc_url_raw( $url ),
|
|
||||||
'comment_content' => \addslashes( $activity['object']['content'] ),
|
|
||||||
'comment_type' => 'comment',
|
|
||||||
'comment_author_email' => '',
|
|
||||||
'comment_parent' => $parent_comment_id ? $parent_comment_id : 0,
|
|
||||||
'comment_meta' => array(
|
|
||||||
'source_id' => \esc_url_raw( $activity['object']['id'] ),
|
|
||||||
'protocol' => 'activitypub',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( isset( $meta['icon']['url'] ) ) {
|
|
||||||
$commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $meta['icon']['url'] );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( isset( $activity['object']['url'] ) ) {
|
|
||||||
$commentdata['comment_meta']['source_url'] = \esc_url_raw( object_to_uri( $activity['object']['url'] ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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' );
|
|
||||||
// No nonce possible for this submission route
|
|
||||||
\add_filter(
|
|
||||||
'akismet_comment_nonce',
|
|
||||||
function () {
|
|
||||||
return 'inactive';
|
|
||||||
}
|
|
||||||
);
|
|
||||||
\add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
|
|
||||||
|
|
||||||
$comment = \wp_new_comment( $commentdata, true );
|
|
||||||
|
|
||||||
\remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10 );
|
|
||||||
\remove_filter( 'pre_option_require_name_email', '__return_false' );
|
|
||||||
// re-add flood control
|
|
||||||
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
|
||||||
|
|
||||||
return $comment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,35 +73,54 @@ class Interactions {
|
||||||
//found a local comment id
|
//found a local comment id
|
||||||
$commentdata['comment_author'] = \esc_attr( $meta['name'] ? $meta['name'] : $meta['preferredUsername'] );
|
$commentdata['comment_author'] = \esc_attr( $meta['name'] ? $meta['name'] : $meta['preferredUsername'] );
|
||||||
$commentdata['comment_content'] = \addslashes( $activity['object']['content'] );
|
$commentdata['comment_content'] = \addslashes( $activity['object']['content'] );
|
||||||
if ( isset( $meta['icon']['url'] ) ) {
|
|
||||||
$commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $meta['icon']['url'] );
|
return self::persist( $commentdata, self::UPDATE );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an incoming Like, Announce, ... as a comment to a post.
|
||||||
|
*
|
||||||
|
* @param array $activity Activity array.
|
||||||
|
*
|
||||||
|
* @return array|false Comment data or `false` on failure.
|
||||||
|
*/
|
||||||
|
public static function add_reaction( $activity ) {
|
||||||
|
$commentdata = self::activity_to_comment( $activity );
|
||||||
|
|
||||||
|
if ( ! $commentdata ) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable flood control
|
$url = object_to_uri( $activity['object'] );
|
||||||
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
$comment_post_id = url_to_postid( $url );
|
||||||
// do not require email for AP entries
|
$parent_comment_id = url_to_commentid( $url );
|
||||||
\add_filter( 'pre_option_require_name_email', '__return_false' );
|
|
||||||
// No nonce possible for this submission route
|
|
||||||
\add_filter(
|
|
||||||
'akismet_comment_nonce',
|
|
||||||
function () {
|
|
||||||
return 'inactive';
|
|
||||||
}
|
|
||||||
);
|
|
||||||
\add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
|
|
||||||
|
|
||||||
$state = \wp_update_comment( $commentdata, true );
|
if ( ! $comment_post_id && $parent_comment_id ) {
|
||||||
|
$parent_comment = get_comment( $parent_comment_id );
|
||||||
\remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10 );
|
$comment_post_id = $parent_comment->comment_post_ID;
|
||||||
\remove_filter( 'pre_option_require_name_email', '__return_false' );
|
|
||||||
// re-add flood control
|
|
||||||
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
|
||||||
|
|
||||||
if ( 1 === $state ) {
|
|
||||||
return $commentdata;
|
|
||||||
} else {
|
|
||||||
return $state; // Either `false` or a `WP_Error` instance or `0` or `1`!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! $comment_post_id ) {
|
||||||
|
// Not a reply to a post or comment.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = $activity['type'];
|
||||||
|
|
||||||
|
if ( ! Comment::is_registered_comment_type( $type ) ) {
|
||||||
|
// Not a valid comment type.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment_type = Comment::get_comment_type( $type );
|
||||||
|
$comment_content = $comment_type['excerpt'];
|
||||||
|
|
||||||
|
$commentdata['comment_post_ID'] = $comment_post_id;
|
||||||
|
$commentdata['comment_content'] = esc_html( $comment_content );
|
||||||
|
$commentdata['comment_type'] = $comment_type['type'];
|
||||||
|
$commentdata['comment_parent'] = $parent_comment_id ? $parent_comment_id : 0;
|
||||||
|
|
||||||
|
return self::persist( $commentdata, self::INSERT );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,4 +214,103 @@ class Interactions {
|
||||||
|
|
||||||
return $allowed_tags;
|
return $allowed_tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an Activity to a WP_Comment
|
||||||
|
*
|
||||||
|
* @param array $activity The Activity array
|
||||||
|
*
|
||||||
|
* @return array|false The commentdata or false on failure
|
||||||
|
*/
|
||||||
|
public static function activity_to_comment( $activity ) {
|
||||||
|
$comment_content = null;
|
||||||
|
$actor = object_to_uri( $activity['actor'] );
|
||||||
|
$actor = get_remote_metadata_by_actor( $actor );
|
||||||
|
|
||||||
|
// check Actor-Meta
|
||||||
|
if ( ! $actor || is_wp_error( $actor ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check Actor-Name
|
||||||
|
if ( isset( $actor['name'] ) ) {
|
||||||
|
$comment_author = $actor['name'];
|
||||||
|
} elseif ( isset( $actor['preferredUsername'] ) ) {
|
||||||
|
$comment_author = $actor['preferredUsername'];
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = object_to_uri( $actor['url'] );
|
||||||
|
|
||||||
|
if ( ! $url ) {
|
||||||
|
object_to_uri( $actor['id'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $activity['object']['content'] ) ) {
|
||||||
|
$comment_content = \addslashes( $activity['object']['content'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$commentdata = array(
|
||||||
|
'comment_author' => \esc_attr( $comment_author ),
|
||||||
|
'comment_author_url' => \esc_url_raw( $url ),
|
||||||
|
'comment_content' => $comment_content,
|
||||||
|
'comment_type' => 'comment',
|
||||||
|
'comment_author_email' => '',
|
||||||
|
'comment_meta' => array(
|
||||||
|
'source_id' => \esc_url_raw( object_to_uri( $activity['object'] ) ),
|
||||||
|
'protocol' => 'activitypub',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( isset( $actor['icon']['url'] ) ) {
|
||||||
|
$commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $actor['icon']['url'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $activity['object']['url'] ) ) {
|
||||||
|
$commentdata['comment_meta']['source_url'] = \esc_url_raw( object_to_uri( $activity['object']['url'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $commentdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist a comment
|
||||||
|
*
|
||||||
|
* @param array $commentdata The commentdata array
|
||||||
|
* @param string $action Either 'insert' or 'update'
|
||||||
|
*
|
||||||
|
* @return array|string|int|\WP_Error|false The commentdata or false on failure
|
||||||
|
*/
|
||||||
|
public static function persist( $commentdata, $action = self::INSERT ) {
|
||||||
|
// 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' );
|
||||||
|
// No nonce possible for this submission route
|
||||||
|
\add_filter(
|
||||||
|
'akismet_comment_nonce',
|
||||||
|
function () {
|
||||||
|
return 'inactive';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
\add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
|
||||||
|
|
||||||
|
if ( self::INSERT === $action ) {
|
||||||
|
$state = \wp_new_comment( $commentdata, true );
|
||||||
|
} else {
|
||||||
|
$state = \wp_update_comment( $commentdata, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
\remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10 );
|
||||||
|
\remove_filter( 'pre_option_require_name_email', '__return_false' );
|
||||||
|
// re-add flood control
|
||||||
|
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||||
|
|
||||||
|
if ( 1 === $state ) {
|
||||||
|
return $commentdata;
|
||||||
|
} else {
|
||||||
|
return $state; // Either `WP_Comment`, `false` or a `WP_Error` instance or `0` or `1`!
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -743,8 +743,13 @@ function object_to_uri( $object ) { // phpcs:ignore Universal.NamingConventions.
|
||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$type = 'Object';
|
||||||
|
if ( isset( $object['type'] ) ) {
|
||||||
|
$type = $object['type'];
|
||||||
|
}
|
||||||
|
|
||||||
// return part of Object that makes most sense
|
// return part of Object that makes most sense
|
||||||
switch ( $object['type'] ) {
|
switch ( $type ) {
|
||||||
case 'Link':
|
case 'Link':
|
||||||
$object = $object['href'];
|
$object = $object['href'];
|
||||||
break;
|
break;
|
||||||
|
@ -1012,6 +1017,39 @@ function custom_large_numbers( $formatted, $number, $decimals ) {
|
||||||
return $formatted;
|
return $formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a ActivityPub comment type.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param string $comment_type Key for comment type.
|
||||||
|
* @param array $args Arguments.
|
||||||
|
*
|
||||||
|
* @return array The registered Activitypub comment type.
|
||||||
|
*/
|
||||||
|
function register_comment_type( $comment_type, $args = array() ) {
|
||||||
|
global $activitypub_comment_types;
|
||||||
|
|
||||||
|
if ( ! is_array( $activitypub_comment_types ) ) {
|
||||||
|
$activitypub_comment_types = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize comment type name.
|
||||||
|
$comment_type = sanitize_key( $comment_type );
|
||||||
|
|
||||||
|
$activitypub_comment_types[ $comment_type ] = $args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires after a ActivityPub comment type is registered.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param string $comment_type Comment type.
|
||||||
|
* @param array $args Arguments used to register the comment type.
|
||||||
|
*/
|
||||||
|
do_action( 'activitypub_registered_comment_type', $comment_type, $args );
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize a URL.
|
* Normalize a URL.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
namespace Activitypub\Handler;
|
namespace Activitypub\Handler;
|
||||||
|
|
||||||
use Activitypub\Http;
|
use Activitypub\Http;
|
||||||
|
use Activitypub\Comment;
|
||||||
|
use Activitypub\Collection\Interactions;
|
||||||
|
|
||||||
|
use function Activitypub\object_to_uri;
|
||||||
use function Activitypub\is_activity_public;
|
use function Activitypub\is_activity_public;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +48,9 @@ class Announce {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo save the `Announce`-Activity itself
|
if ( ! ACTIVITYPUB_DISABLE_REACTIONS ) {
|
||||||
|
self::maybe_save_announce( $array, $user_id, $activity );
|
||||||
|
}
|
||||||
|
|
||||||
if ( is_string( $array['object'] ) ) {
|
if ( is_string( $array['object'] ) ) {
|
||||||
$object = Http::get_remote_object( $array['object'] );
|
$object = Http::get_remote_object( $array['object'] );
|
||||||
|
@ -66,4 +71,35 @@ class Announce {
|
||||||
\do_action( 'activitypub_inbox', $object, $user_id, $type, $activity );
|
\do_action( 'activitypub_inbox', $object, $user_id, $type, $activity );
|
||||||
\do_action( "activitypub_inbox_{$type}", $object, $user_id, $activity );
|
\do_action( "activitypub_inbox_{$type}", $object, $user_id, $activity );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to save the Announce
|
||||||
|
*
|
||||||
|
* @param array $array The activity-object
|
||||||
|
* @param int $user_id The id of the local blog-user
|
||||||
|
* @param Activitypub\Activity $activity The activity object
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function maybe_save_announce( $array, $user_id, $activity ) { // phpcs:ignore
|
||||||
|
$url = object_to_uri( $array['object'] );
|
||||||
|
|
||||||
|
if ( empty( $url ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = Comment::object_id_to_comment( esc_url_raw( $url ) );
|
||||||
|
if ( $exists ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = Interactions::add_reaction( $array );
|
||||||
|
$reaction = null;
|
||||||
|
|
||||||
|
if ( $state && ! is_wp_error( $state ) ) {
|
||||||
|
$reaction = get_comment( $state );
|
||||||
|
}
|
||||||
|
|
||||||
|
do_action( 'activitypub_handled_announce', $array, $user_id, $state, $reaction );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
59
includes/handler/class-like.php
Normal file
59
includes/handler/class-like.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
namespace Activitypub\Handler;
|
||||||
|
|
||||||
|
use Activitypub\Comment;
|
||||||
|
use Activitypub\Collection\Interactions;
|
||||||
|
|
||||||
|
use function Activitypub\object_to_uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Like requests
|
||||||
|
*/
|
||||||
|
class Like {
|
||||||
|
/**
|
||||||
|
* Initialize the class, registering WordPress hooks
|
||||||
|
*/
|
||||||
|
public static function init() {
|
||||||
|
\add_action(
|
||||||
|
'activitypub_inbox_like',
|
||||||
|
array( self::class, 'handle_like' ),
|
||||||
|
10,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles "Like" requests
|
||||||
|
*
|
||||||
|
* @param array $array The Activity array.
|
||||||
|
* @param int $user_id The ID of the local blog user.
|
||||||
|
* @param \Activitypub\Activity $activity The Activity object.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function handle_like( $array, $user_id, $activity = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound,VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable,Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||||
|
if ( ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = object_to_uri( $array['object'] );
|
||||||
|
|
||||||
|
if ( empty( $url ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = Comment::object_id_to_comment( esc_url_raw( $url ) );
|
||||||
|
if ( $exists ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = Interactions::add_reaction( $array );
|
||||||
|
$reaction = null;
|
||||||
|
|
||||||
|
if ( $state && ! is_wp_error( $state ) ) {
|
||||||
|
$reaction = get_comment( $state );
|
||||||
|
}
|
||||||
|
|
||||||
|
do_action( 'activitypub_handled_like', $array, $user_id, $state, $reaction );
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ namespace Activitypub\Handler;
|
||||||
|
|
||||||
use Activitypub\Collection\Users;
|
use Activitypub\Collection\Users;
|
||||||
use Activitypub\Collection\Followers;
|
use Activitypub\Collection\Followers;
|
||||||
|
use Activitypub\Comment;
|
||||||
|
|
||||||
use function Activitypub\object_to_uri;
|
use function Activitypub\object_to_uri;
|
||||||
|
|
||||||
|
@ -28,10 +29,16 @@ class Undo {
|
||||||
*/
|
*/
|
||||||
public static function handle_undo( $activity ) {
|
public static function handle_undo( $activity ) {
|
||||||
if (
|
if (
|
||||||
isset( $activity['object']['type'] ) &&
|
! isset( $activity['object']['type'] ) ||
|
||||||
'Follow' === $activity['object']['type'] &&
|
! isset( $activity['object']['object'] )
|
||||||
isset( $activity['object']['object'] )
|
|
||||||
) {
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = $activity['object']['type'];
|
||||||
|
|
||||||
|
// Handle "Unfollow" requests
|
||||||
|
if ( 'Follow' === $type ) {
|
||||||
$user_id = object_to_uri( $activity['object']['object'] );
|
$user_id = object_to_uri( $activity['object']['object'] );
|
||||||
$user = Users::get_by_resource( $user_id );
|
$user = Users::get_by_resource( $user_id );
|
||||||
|
|
||||||
|
@ -46,5 +53,23 @@ class Undo {
|
||||||
|
|
||||||
Followers::remove_follower( $user_id, $actor );
|
Followers::remove_follower( $user_id, $actor );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle "Undo" requests for "Like" and "Create" activities
|
||||||
|
if ( in_array( $type, array( 'Like', 'Create', 'Announce' ), true ) ) {
|
||||||
|
if ( ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$object_id = object_to_uri( $activity['object'] );
|
||||||
|
$comment = Comment::object_id_to_comment( esc_url_raw( $object_id ) );
|
||||||
|
|
||||||
|
if ( empty( $comment ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = wp_trash_comment( $comment );
|
||||||
|
|
||||||
|
do_action( 'activitypub_handled_undo', $activity, $user_id, isset( $state ) ? $state : null, null );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
\define( 'ACTIVITYPUB_DISABLE_REACTIONS', false );
|
||||||
|
|
||||||
$_tests_dir = \getenv( 'WP_TESTS_DIR' );
|
$_tests_dir = \getenv( 'WP_TESTS_DIR' );
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,12 @@ class Test_Activitypub_Create_Handler extends WP_UnitTestCase {
|
||||||
);
|
);
|
||||||
$this->post_permalink = \get_permalink( $this->post_id );
|
$this->post_permalink = \get_permalink( $this->post_id );
|
||||||
|
|
||||||
\add_filter( 'pre_get_remote_metadata_by_actor', array( '\Test_Activitypub_Create_Handler', 'get_remote_metadata_by_actor' ), 0, 2 );
|
\add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'get_remote_metadata_by_actor' ), 0, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tear_down() {
|
||||||
|
\remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'get_remote_metadata_by_actor' ) );
|
||||||
|
parent::tear_down();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_remote_metadata_by_actor( $value, $actor ) {
|
public static function get_remote_metadata_by_actor( $value, $actor ) {
|
||||||
|
|
|
@ -52,12 +52,12 @@ class Test_Activitypub_Followers extends WP_UnitTestCase {
|
||||||
|
|
||||||
public function set_up() {
|
public function set_up() {
|
||||||
parent::set_up();
|
parent::set_up();
|
||||||
add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
|
\add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
|
||||||
_delete_all_posts();
|
_delete_all_posts();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tear_down() {
|
public function tear_down() {
|
||||||
remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );
|
\remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );
|
||||||
parent::tear_down();
|
parent::tear_down();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
83
tests/test-class-activitypub-reaction-handler.php
Normal file
83
tests/test-class-activitypub-reaction-handler.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
class Test_Activitypub_Reaction_Handler extends WP_UnitTestCase {
|
||||||
|
public $user_id;
|
||||||
|
public $user_url;
|
||||||
|
public $post_id;
|
||||||
|
public $post_permalink;
|
||||||
|
|
||||||
|
public function set_up() {
|
||||||
|
parent::set_up();
|
||||||
|
$this->user_id = 1;
|
||||||
|
$authordata = \get_userdata( $this->user_id );
|
||||||
|
$this->user_url = $authordata->user_url;
|
||||||
|
|
||||||
|
$this->post_id = \wp_insert_post(
|
||||||
|
array(
|
||||||
|
'post_author' => $this->user_id,
|
||||||
|
'post_content' => 'test',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$this->post_permalink = \get_permalink( $this->post_id );
|
||||||
|
|
||||||
|
\add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'get_remote_metadata_by_actor' ), 0, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tear_down() {
|
||||||
|
\remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'get_remote_metadata_by_actor' ) );
|
||||||
|
parent::tear_down();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_remote_metadata_by_actor( $value, $actor ) {
|
||||||
|
return array(
|
||||||
|
'name' => 'Example User',
|
||||||
|
'icon' => array(
|
||||||
|
'url' => 'https://example.com/icon',
|
||||||
|
),
|
||||||
|
'url' => $actor,
|
||||||
|
'id' => 'http://example.org/users/example',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create_test_object() {
|
||||||
|
return array(
|
||||||
|
'actor' => $this->user_url,
|
||||||
|
'type' => 'Like',
|
||||||
|
'id' => 'https://example.com/id/' . microtime( true ),
|
||||||
|
'to' => [ $this->user_url ],
|
||||||
|
'cc' => [ 'https://www.w3.org/ns/activitystreams#Public' ],
|
||||||
|
'object' => $this->post_permalink,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_handle_like() {
|
||||||
|
$object = $this->create_test_object();
|
||||||
|
Activitypub\Handler\Like::handle_like( $object, $this->user_id );
|
||||||
|
|
||||||
|
$args = array(
|
||||||
|
'type' => 'like',
|
||||||
|
'post_id' => $this->post_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
$query = new \WP_Comment_Query( $args );
|
||||||
|
$result = $query->comments;
|
||||||
|
|
||||||
|
$this->assertInstanceOf( 'WP_Comment', $result[0] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_handle_announce() {
|
||||||
|
$object = $this->create_test_object();
|
||||||
|
$object['type'] = 'Announce';
|
||||||
|
|
||||||
|
Activitypub\Handler\Announce::handle_announce( $object, $this->user_id );
|
||||||
|
|
||||||
|
$args = array(
|
||||||
|
'type' => 'repost',
|
||||||
|
'post_id' => $this->post_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
$query = new \WP_Comment_Query( $args );
|
||||||
|
$result = $query->comments;
|
||||||
|
|
||||||
|
$this->assertInstanceOf( 'WP_Comment', $result[0] );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue