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_DISABLE_REWRITES' ) || \define( 'ACTIVITYPUB_DISABLE_REWRITES', 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_SHARED_INBOX_FEATURE' ) || \define( 'ACTIVITYPUB_SHARED_INBOX_FEATURE', false );
|
||||
\defined( 'ACTIVITYPUB_SEND_VARY_HEADER' ) || \define( 'ACTIVITYPUB_SEND_VARY_HEADER', false );
|
||||
|
|
|
@ -19,6 +19,8 @@ class Comment {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
self::register_comment_types();
|
||||
|
||||
\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( '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\Delete;
|
||||
use Activitypub\Handler\Follow;
|
||||
use Activitypub\Handler\Like;
|
||||
use Activitypub\Handler\Undo;
|
||||
use Activitypub\Handler\Update;
|
||||
|
||||
|
@ -30,6 +31,10 @@ class Handler {
|
|||
Undo::init();
|
||||
Update::init();
|
||||
|
||||
if ( ! ACTIVITYPUB_DISABLE_REACTIONS ) {
|
||||
Like::init();
|
||||
}
|
||||
|
||||
do_action( 'activitypub_register_handlers' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ namespace Activitypub\Collection;
|
|||
|
||||
use WP_Error;
|
||||
use WP_Comment_Query;
|
||||
use Activitypub\Comment;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
use function Activitypub\url_to_commentid;
|
||||
|
@ -13,6 +14,9 @@ use function Activitypub\get_remote_metadata_by_actor;
|
|||
* ActivityPub Interactions Collection
|
||||
*/
|
||||
class Interactions {
|
||||
const INSERT = 'insert';
|
||||
const UPDATE = 'update';
|
||||
|
||||
/**
|
||||
* Add a comment to a post
|
||||
*
|
||||
|
@ -21,20 +25,15 @@ class Interactions {
|
|||
* @return array|false The commentdata or false on failure
|
||||
*/
|
||||
public static function add_comment( $activity ) {
|
||||
if (
|
||||
! isset( $activity['object'] ) ||
|
||||
! isset( $activity['object']['id'] )
|
||||
) {
|
||||
$commentdata = self::activity_to_comment( $activity );
|
||||
|
||||
if ( ! $commentdata || ! isset( $activity['object']['inReplyTo'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $activity['object']['inReplyTo'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$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 );
|
||||
$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
|
||||
if ( ! $comment_post_id && $parent_comment_id ) {
|
||||
|
@ -47,58 +46,10 @@ class Interactions {
|
|||
return false;
|
||||
}
|
||||
|
||||
$actor = object_to_uri( $activity['actor'] );
|
||||
$meta = get_remote_metadata_by_actor( $actor );
|
||||
$commentdata['comment_post_ID'] = $comment_post_id;
|
||||
$commentdata['comment_parent'] = $parent_comment_id ? $parent_comment_id : 0;
|
||||
|
||||
if ( ! $meta || \is_wp_error( $meta ) ) {
|
||||
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;
|
||||
return self::persist( $commentdata, self::INSERT );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,35 +73,54 @@ class Interactions {
|
|||
//found a local comment id
|
||||
$commentdata['comment_author'] = \esc_attr( $meta['name'] ? $meta['name'] : $meta['preferredUsername'] );
|
||||
$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
|
||||
\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 );
|
||||
$url = object_to_uri( $activity['object'] );
|
||||
$comment_post_id = url_to_postid( $url );
|
||||
$parent_comment_id = url_to_commentid( $url );
|
||||
|
||||
$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 `false` or a `WP_Error` instance or `0` or `1`!
|
||||
if ( ! $comment_post_id && $parent_comment_id ) {
|
||||
$parent_comment = get_comment( $parent_comment_id );
|
||||
$comment_post_id = $parent_comment->comment_post_ID;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$type = 'Object';
|
||||
if ( isset( $object['type'] ) ) {
|
||||
$type = $object['type'];
|
||||
}
|
||||
|
||||
// return part of Object that makes most sense
|
||||
switch ( $object['type'] ) {
|
||||
switch ( $type ) {
|
||||
case 'Link':
|
||||
$object = $object['href'];
|
||||
break;
|
||||
|
@ -1012,6 +1017,39 @@ function custom_large_numbers( $formatted, $number, $decimals ) {
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
namespace Activitypub\Handler;
|
||||
|
||||
use Activitypub\Http;
|
||||
use Activitypub\Comment;
|
||||
use Activitypub\Collection\Interactions;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
use function Activitypub\is_activity_public;
|
||||
|
||||
/**
|
||||
|
@ -45,7 +48,9 @@ class Announce {
|
|||
return;
|
||||
}
|
||||
|
||||
// @todo save the `Announce`-Activity itself
|
||||
if ( ! ACTIVITYPUB_DISABLE_REACTIONS ) {
|
||||
self::maybe_save_announce( $array, $user_id, $activity );
|
||||
}
|
||||
|
||||
if ( is_string( $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_{$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\Followers;
|
||||
use Activitypub\Comment;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
|
@ -28,10 +29,16 @@ class Undo {
|
|||
*/
|
||||
public static function handle_undo( $activity ) {
|
||||
if (
|
||||
isset( $activity['object']['type'] ) &&
|
||||
'Follow' === $activity['object']['type'] &&
|
||||
isset( $activity['object']['object'] )
|
||||
! isset( $activity['object']['type'] ) ||
|
||||
! isset( $activity['object']['object'] )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = $activity['object']['type'];
|
||||
|
||||
// Handle "Unfollow" requests
|
||||
if ( 'Follow' === $type ) {
|
||||
$user_id = object_to_uri( $activity['object']['object'] );
|
||||
$user = Users::get_by_resource( $user_id );
|
||||
|
||||
|
@ -46,5 +53,23 @@ class Undo {
|
|||
|
||||
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
|
||||
\define( 'ACTIVITYPUB_DISABLE_REACTIONS', false );
|
||||
|
||||
$_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 );
|
||||
|
||||
\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 ) {
|
||||
|
|
|
@ -52,12 +52,12 @@ class Test_Activitypub_Followers extends WP_UnitTestCase {
|
|||
|
||||
public function 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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
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