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:
Jan Boddez 2024-08-16 12:55:16 +02:00 committed by GitHub
parent 7806285d88
commit dabff80cc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 531 additions and 95 deletions

View file

@ -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 );

View file

@ -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' => __( '&hellip; 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' => __( '&hellip; liked this!', 'activitypub' ),
)
);
}
} }

View file

@ -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' );
} }
} }

View file

@ -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`!
}
}
} }

View file

@ -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.
* *

View file

@ -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 );
}
} }

View 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 );
}
}

View file

@ -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 );
}
} }
} }

View file

@ -1,4 +1,5 @@
<?php <?php
\define( 'ACTIVITYPUB_DISABLE_REACTIONS', false );
$_tests_dir = \getenv( 'WP_TESTS_DIR' ); $_tests_dir = \getenv( 'WP_TESTS_DIR' );

View file

@ -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 ) {

View file

@ -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();
} }

View 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] );
}
}