Comment Federation (#550)
* Comments 1 * Delete FUNDING.yml * Add basic BuddyPress support fix #122 thanks and props @skysarwer * change URL to `bp_core_get_user_domain` * fix "Follow" issue fix #133 * fix #135 * version bump * Create phpunit.yml * Update composer.json * Update composer.json * Update phpunit.yml * Update composer.json * Create phpcs.yml * Update phpcs.xml * Update composer.json * phpcs fixes * fix typo * Comments update * webfinger_extract remove extra param * coding standards * Replies Collection, settings, other fixes * Create stale.yml * move stale file * code standards cleanup * Migrate / Update script * bugfix * add settings link to plugin page * fix code standards * fix cs * fix PHPCS * PHPCS fixes * change background image for wp.org * fix docker * fix webfinger for email identifiers fix #152 * version bump * update composer file to fix unit testing * allow plugins * fix dependencies * Migrate tools * code cleanup * regression fix * Fix announce, clarified language * update included filename * code cleanup * Improve migration UX * Add comments view, warnings to migrate page * style fix * more style fixes * Fix send_delete_activity * replace ap_comment_id to reuse replytocom var * Comments class missing attributes * Post class fix attributes * move js file to assets/js * Separate file for Comment processing hooks * fix file path * associate comments to back compat post * Fix js assets enqueue * change regex matching potential hashtags Matches any string starting with '#' and consisting of any number and combination of [A-Za-z0-9_] that is directly followed by whitespace or punctuation. Groups everything after '#' for access in functions using this regex. This fixes #183 (incomplete links on hashtags containing special characters) by not matching these at all. * also detect hashtags at the start of a paragraph * restrict html tags after which to detect a hashtag Hashtags should not be detected after just any html tag - for example not after an opening a or div. To still allow detection at the start of a line, allow specifically p and br to directly precede a hashtag. * fix pagination * Add Custom Post Type support to outbox API * remove comment_type * fix comparison * remove trailing spaces * fix phpcs issues * fix phpcs issues * run phpcs also on pull_requests * fix phpcs issues * support threaded comments from ActivityPub * refactor support for threaded comments from ActivityPub * remove debugging log line * add first unit tests for class inbox * fix code smells * make filter function static * attempt to resolve backwards compatibility issues * update js to new file * delete old js * Remove migrate code * update post meta canonical * remove type and mention meta from comment filters * extract mentions from comment_content * phpcbf * remove extra curly bracket * Remove migrate code * remove version_check() * Update enqueue scripts * Remove remote comments from preprocessing * Reply to comments from Dashboard * rename function, inserts users into reply text * Update dispatch comments * update comment model * fix comment model replies property * fix preprocess_comment cap check * Add webfinger filter to comments * Add comment edit datetime * cleanup * fix var name * cleanup * phpcbf * better actual translation support * Separate comment reply script * migrate dispatch, migrate comment model to transform * ignore WP_Comment type for now * Adds new helpers for resolving inReplyTo url * Update activitypub_send_comment_activity to include type * remove redundant id check * reinclude user_id in saved ap_object meta * update post field meta * Fix comment updated datetime * front-end reply inserts @mentions * enqueue reply script on front end * use const instead of dirname * some simplifications * move some functions * fixes * some more fixes * fix namespace * fix unittests * fix testcase * fixed typo * fix tests * fix tests * fix PHPCS * move functions to transformer class * fix warnings * Link remote comments on frontend * Link to comment source as row action * Init Comments class * remove dead dispatch action * re-add extract mentions filter * Restore and tweak Comment transform * Schedule comments activities for non-admin users * lint * remove context property * rename get_id method to generate_id * fix locale * move functions * PHPDoc * this is never used * remove some edit methods * remove replies for now * remove JS calls * remove reply_recipients * never used * remove other query-vars * otherwise to_json would not work properly * small changes * use `c` for comment IDs * remove comments.php for now maybe re-add it later * wp_insert_post is an action * also parse comment_text * remove duplicate functions * add Base transformer * remove invalid test * update to new query var * update dispatcher to support comments and posts * fix transition * remove unused functions for now * schedule_comment_activity seems to ignore create and update * fix wrong use of functions! * not every platforms sends an URL * check source-id first * remove hashtags for now * fallback to ID * fix typo * move to_activity to Base class * remove unused function * add support for announce and like * also ping inboxes of other commenters in the thread * restructure WebFinger class * some small improvements * simplified to_object class props @Menrath for the feedback and the idea! * fix unit tests * make transformer filterable /cc @Menrath * use transformer factory, so that transformer can be overwritten * phpcs fixes * fix attachments * fix comment transformer * remove comments for now * update readme/changelog * simplify and unify json_encodes --------- Co-authored-by: Django Doucet <mediaformat.ux@gmail.com> Co-authored-by: Andreas <andreas@bocops.de> Co-authored-by: Eana Hufwe <eana@1a23.com> Co-authored-by: Matthew Exon <git.mexon@spamgourmet.com> Co-authored-by: Django Doucet <django.doucet@webdevstudios.com>
This commit is contained in:
parent
a3ea9955d9
commit
b744dc551d
24 changed files with 1009 additions and 278 deletions
|
@ -113,6 +113,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu
|
||||||
* Added: Make Post-Template filterable
|
* Added: Make Post-Template filterable
|
||||||
* Added: CSS class for ActivityPub comments to allow custom designs
|
* Added: CSS class for ActivityPub comments to allow custom designs
|
||||||
* Added: FEP-2677: Identifying the Application Actor
|
* Added: FEP-2677: Identifying the Application Actor
|
||||||
|
* Added: Basic Comment Federation
|
||||||
* Improved: WebFinger endpoints
|
* Improved: WebFinger endpoints
|
||||||
|
|
||||||
### 1.3.0 ###
|
### 1.3.0 ###
|
||||||
|
|
|
@ -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 );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -254,19 +254,6 @@ class Base_Object {
|
||||||
*/
|
*/
|
||||||
protected $published;
|
protected $published;
|
||||||
|
|
||||||
/**
|
|
||||||
* A Collection containing objects considered to be responses to
|
|
||||||
* this object.
|
|
||||||
*
|
|
||||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-replies
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
* | Collection
|
|
||||||
* | Link
|
|
||||||
* | null
|
|
||||||
*/
|
|
||||||
protected $replies;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The date and time describing the actual or expected starting time
|
* The date and time describing the actual or expected starting time
|
||||||
* of the object.
|
* of the object.
|
||||||
|
@ -437,6 +424,19 @@ class Base_Object {
|
||||||
*/
|
*/
|
||||||
protected $source;
|
protected $source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Collection containing objects considered to be responses to
|
||||||
|
* this object.
|
||||||
|
*
|
||||||
|
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-replies
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* | Collection
|
||||||
|
* | Link
|
||||||
|
* | null
|
||||||
|
*/
|
||||||
|
protected $replies;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Magic function to implement getter and setter
|
* Magic function to implement getter and setter
|
||||||
*
|
*
|
||||||
|
@ -671,8 +671,25 @@ class Base_Object {
|
||||||
* @return string The JSON string.
|
* @return string The JSON string.
|
||||||
*/
|
*/
|
||||||
public function to_json() {
|
public function to_json() {
|
||||||
$array = $this->to_array();
|
$array = $this->to_array();
|
||||||
|
$options = \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT;
|
||||||
|
|
||||||
return \wp_json_encode( $array, \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_encode_options', $options );
|
||||||
|
|
||||||
|
return \wp_json_encode( $array, $options );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the keys of the object vars.
|
||||||
|
*
|
||||||
|
* @return array The keys of the object vars.
|
||||||
|
*/
|
||||||
|
public function get_object_var_keys() {
|
||||||
|
return \array_keys( \get_object_vars( $this ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,37 @@ class Activity_Dispatcher {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$wp_post->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, Users::BLOG_USER_ID );
|
||||||
} 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, $user_id = null ) {
|
||||||
if ( is_user_disabled( $wp_post->post_author ) ) {
|
$transformer = Factory::get_transformer( $wp_object );
|
||||||
|
|
||||||
|
if ( null !== $user_id ) {
|
||||||
|
$transformer->change_wp_user_id( $user_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = $transformer->get_wp_user_id();
|
||||||
|
|
||||||
|
if ( is_user_disabled( $user_id ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$object = Post::transform( $wp_post )->to_object();
|
$activity = $transformer->to_activity( 'Create' );
|
||||||
|
|
||||||
$activity = new Activity();
|
$follower_inboxes = Followers::get_inboxes( $user_id );
|
||||||
$activity->set_type( $type );
|
|
||||||
$activity->set_object( $object );
|
|
||||||
|
|
||||||
$follower_inboxes = Followers::get_inboxes( $wp_post->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 +85,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, $user_id );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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,16 +106,13 @@ class Activity_Dispatcher {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$object = Post::transform( $wp_post )->to_object();
|
$transformer = Factory::get_transformer( $wp_object );
|
||||||
|
$transformer->change_wp_user_id( Users::BLOG_USER_ID );
|
||||||
|
|
||||||
$activity = new Activity();
|
$user_id = $transformer->get_wp_user_id();
|
||||||
$activity->set_type( 'Announce' );
|
$activity = $transformer->to_activity( 'Announce' );
|
||||||
// to pre-fill attributes like "published" and "id"
|
|
||||||
$activity->set_object( $object );
|
|
||||||
// send only the id
|
|
||||||
$activity->set_object( $object->get_id() );
|
|
||||||
|
|
||||||
$follower_inboxes = Followers::get_inboxes( $wp_post->post_author );
|
$follower_inboxes = Followers::get_inboxes( $user_id );
|
||||||
$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 +121,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, $user_id );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ use Activitypub\Collection\Followers;
|
||||||
|
|
||||||
use function Activitypub\sanitize_url;
|
use function Activitypub\sanitize_url;
|
||||||
|
|
||||||
|
use function Activitypub\is_comment;
|
||||||
|
use function Activitypub\is_activitypub_request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActivityPub Class
|
* ActivityPub Class
|
||||||
*
|
*
|
||||||
|
@ -19,6 +22,7 @@ class Activitypub {
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
\add_filter( 'template_include', array( self::class, 'render_json_template' ), 99 );
|
\add_filter( 'template_include', array( self::class, 'render_json_template' ), 99 );
|
||||||
|
\add_action( 'template_redirect', array( self::class, 'template_redirect' ) );
|
||||||
\add_filter( 'query_vars', array( self::class, 'add_query_vars' ) );
|
\add_filter( 'query_vars', array( self::class, 'add_query_vars' ) );
|
||||||
\add_filter( 'pre_get_avatar_data', array( self::class, 'pre_get_avatar_data' ), 11, 2 );
|
\add_filter( 'pre_get_avatar_data', array( self::class, 'pre_get_avatar_data' ), 11, 2 );
|
||||||
\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 );
|
||||||
|
@ -100,6 +104,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() ) {
|
||||||
|
@ -117,11 +123,43 @@ class Activitypub {
|
||||||
return $json_template;
|
return $json_template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom redirects for ActivityPub requests.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function template_redirect() {
|
||||||
|
$comment_id = get_query_var( 'c', null );
|
||||||
|
|
||||||
|
// check if it seems to be a comment
|
||||||
|
if ( ! $comment_id ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment = get_comment( $comment_id );
|
||||||
|
|
||||||
|
// load a 404 page if `c` is set but not valid
|
||||||
|
if ( ! $comment ) {
|
||||||
|
global $wp_query;
|
||||||
|
$wp_query->set_404();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop if it's not an ActivityPub comment
|
||||||
|
if ( is_activitypub_request() && $comment->user_id ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_safe_redirect( get_comment_link( $comment ) );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the 'activitypub' query variable so WordPress won't mangle it.
|
* Add the 'activitypub' query variable so WordPress won't mangle it.
|
||||||
*/
|
*/
|
||||||
public static function add_query_vars( $vars ) {
|
public static function add_query_vars( $vars ) {
|
||||||
$vars[] = 'activitypub';
|
$vars[] = 'activitypub';
|
||||||
|
$vars[] = 'c';
|
||||||
|
$vars[] = 'p';
|
||||||
|
|
||||||
return $vars;
|
return $vars;
|
||||||
}
|
}
|
||||||
|
@ -197,10 +235,18 @@ 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 ( ! $comment || is_admin() ) {
|
||||||
if ( $remote_comment_link ) {
|
return $comment_link;
|
||||||
$comment_link = esc_url( $remote_comment_link );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$comment_meta = \get_comment_meta( $comment->comment_ID );
|
||||||
|
|
||||||
|
if ( ! empty( $comment_meta['source_url'][0] ) ) {
|
||||||
|
return $comment_meta['source_url'][0];
|
||||||
|
} elseif ( ! empty( $comment_meta['source_id'][0] ) ) {
|
||||||
|
return $comment_meta['source_id'][0];
|
||||||
|
}
|
||||||
|
|
||||||
return $comment_link;
|
return $comment_link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ class Hashtag {
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
if ( '1' === \get_option( 'activitypub_use_hashtags', '1' ) ) {
|
if ( '1' === \get_option( 'activitypub_use_hashtags', '1' ) ) {
|
||||||
\add_filter( 'wp_insert_post', array( self::class, 'insert_post' ), 10, 2 );
|
\add_action( 'wp_insert_post', array( self::class, 'insert_post' ), 10, 2 );
|
||||||
\add_filter( 'the_content', array( self::class, 'the_content' ), 10, 2 );
|
\add_filter( 'the_content', array( self::class, 'the_content' ), 10, 1 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,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 );
|
||||||
|
|
|
@ -14,7 +14,8 @@ class Mention {
|
||||||
* Initialize the class, registering WordPress hooks
|
* Initialize the class, registering WordPress hooks
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
\add_filter( 'the_content', array( self::class, 'the_content' ), 99, 2 );
|
\add_filter( 'the_content', array( self::class, 'the_content' ), 99, 1 );
|
||||||
|
\add_filter( 'comment_text', array( self::class, 'the_content' ), 10, 1 );
|
||||||
\add_filter( 'activitypub_extract_mentions', array( self::class, 'extract_mentions' ), 99, 2 );
|
\add_filter( 'activitypub_extract_mentions', array( self::class, 'extract_mentions' ), 99, 2 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,47 @@ class Scheduler {
|
||||||
* Initialize the class, registering WordPress hooks
|
* Initialize the class, registering WordPress hooks
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
|
// Post transitions
|
||||||
\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(
|
||||||
|
'edit_attachment',
|
||||||
|
function ( $post_id ) {
|
||||||
|
self::schedule_post_activity( 'publish', 'publish', $post_id );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
\add_action(
|
||||||
|
'add_attachment',
|
||||||
|
function ( $post_id ) {
|
||||||
|
self::schedule_post_activity( 'publish', '', $post_id );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
\add_action(
|
||||||
|
'delete_attachment',
|
||||||
|
function ( $post_id ) {
|
||||||
|
self::schedule_post_activity( 'trash', '', $post_id );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Comment transitions
|
||||||
|
\add_action( 'transition_comment_status', array( self::class, 'schedule_comment_activity' ), 20, 3 );
|
||||||
|
\add_action(
|
||||||
|
'edit_comment',
|
||||||
|
function ( $comment_id ) {
|
||||||
|
self::schedule_comment_activity( 'approved', 'approved', $comment_id );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
\add_action(
|
||||||
|
'wp_insert_comment',
|
||||||
|
function ( $comment_id ) {
|
||||||
|
self::schedule_comment_activity( 'approved', '', $comment_id );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Follower Cleanups
|
||||||
\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' ) );
|
||||||
|
|
||||||
|
// Migration
|
||||||
\add_action( 'admin_init', array( self::class, 'schedule_migration' ) );
|
\add_action( 'admin_init', array( self::class, 'schedule_migration' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +94,8 @@ class Scheduler {
|
||||||
* @param WP_Post $post Post object.
|
* @param WP_Post $post Post object.
|
||||||
*/
|
*/
|
||||||
public static function schedule_post_activity( $new_status, $old_status, $post ) {
|
public static function schedule_post_activity( $new_status, $old_status, $post ) {
|
||||||
|
$post = get_post( $post );
|
||||||
|
|
||||||
// Do not send activities if post is password protected.
|
// Do not send activities if post is password protected.
|
||||||
if ( \post_password_required( $post ) ) {
|
if ( \post_password_required( $post ) ) {
|
||||||
return;
|
return;
|
||||||
|
@ -99,6 +137,57 @@ 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 );
|
||||||
|
|
||||||
|
// Federate only approved comments.
|
||||||
|
if ( ! $comment->user_id ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
'approved' === $new_status &&
|
||||||
|
'approved' !== $old_status
|
||||||
|
) {
|
||||||
|
$type = 'Create';
|
||||||
|
} elseif ( 'approved' === $new_status ) {
|
||||||
|
$type = 'Update';
|
||||||
|
} elseif (
|
||||||
|
'trash' === $new_status ||
|
||||||
|
'spam' === $new_status
|
||||||
|
) {
|
||||||
|
$type = 'Delete';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $type ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
\wp_schedule_single_event(
|
||||||
|
\time(),
|
||||||
|
'activitypub_send_activity',
|
||||||
|
array( $comment, $type )
|
||||||
|
);
|
||||||
|
|
||||||
|
\wp_schedule_single_event(
|
||||||
|
\time(),
|
||||||
|
sprintf(
|
||||||
|
'activitypub_send_%s_activity',
|
||||||
|
\strtolower( $type )
|
||||||
|
),
|
||||||
|
array( $comment )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update followers
|
* Update followers
|
||||||
*
|
*
|
||||||
|
|
|
@ -110,8 +110,13 @@ class Shortcodes {
|
||||||
|
|
||||||
$excerpt = \get_post_field( 'post_excerpt', $item );
|
$excerpt = \get_post_field( 'post_excerpt', $item );
|
||||||
|
|
||||||
if ( '' === $excerpt ) {
|
if ( 'attachment' === $item->post_type ) {
|
||||||
|
// get title of attachment with fallback to alt text.
|
||||||
|
$content = wp_get_attachment_caption( $item->ID );
|
||||||
|
if ( empty( $content ) ) {
|
||||||
|
$content = get_post_meta( $item->ID, '_wp_attachment_image_alt', true );
|
||||||
|
}
|
||||||
|
} elseif ( '' === $excerpt ) {
|
||||||
$content = \get_post_field( 'post_content', $item );
|
$content = \get_post_field( 'post_content', $item );
|
||||||
|
|
||||||
// An empty string will make wp_trim_excerpt do stuff we do not want.
|
// An empty string will make wp_trim_excerpt do stuff we do not want.
|
||||||
|
@ -207,20 +212,30 @@ class Shortcodes {
|
||||||
$tag
|
$tag
|
||||||
);
|
);
|
||||||
|
|
||||||
$content = \get_post_field( 'post_content', $item );
|
$content = '';
|
||||||
|
|
||||||
if ( 'yes' === $atts['apply_filters'] ) {
|
if ( 'attachment' === $item->post_type ) {
|
||||||
$content = \apply_filters( 'the_content', $content );
|
// get title of attachment with fallback to alt text.
|
||||||
|
$content = wp_get_attachment_caption( $item->ID );
|
||||||
|
if ( empty( $content ) ) {
|
||||||
|
$content = get_post_meta( $item->ID, '_wp_attachment_image_alt', true );
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$content = do_blocks( $content );
|
$content = \get_post_field( 'post_content', $item );
|
||||||
$content = wptexturize( $content );
|
|
||||||
$content = wp_filter_content_tags( $content );
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace script and style elements
|
if ( 'yes' === $atts['apply_filters'] ) {
|
||||||
$content = \preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $content );
|
$content = \apply_filters( 'the_content', $content );
|
||||||
$content = strip_shortcodes( $content );
|
} else {
|
||||||
$content = \trim( \preg_replace( '/[\n\r\t]/', '', $content ) );
|
$content = do_blocks( $content );
|
||||||
|
$content = wptexturize( $content );
|
||||||
|
$content = wp_filter_content_tags( $content );
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace script and style elements
|
||||||
|
$content = \preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $content );
|
||||||
|
$content = strip_shortcodes( $content );
|
||||||
|
$content = \trim( \preg_replace( '/[\n\r\t]/', '', $content ) );
|
||||||
|
}
|
||||||
|
|
||||||
add_shortcode( 'ap_content', array( 'Activitypub\Shortcodes', 'content' ) );
|
add_shortcode( 'ap_content', array( 'Activitypub\Shortcodes', 'content' ) );
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ class Webfinger {
|
||||||
/**
|
/**
|
||||||
* Returns a users WebFinger "resource"
|
* Returns a users WebFinger "resource"
|
||||||
*
|
*
|
||||||
* @param int $user_id
|
* @param int $user_id The WordPress user id
|
||||||
*
|
*
|
||||||
* @return string The user-resource
|
* @return string The user-resource
|
||||||
*/
|
*/
|
||||||
|
@ -36,68 +36,65 @@ class Webfinger {
|
||||||
/**
|
/**
|
||||||
* Resolve a WebFinger resource
|
* Resolve a WebFinger resource
|
||||||
*
|
*
|
||||||
* @param string $resource The WebFinger resource
|
* @param string $uri The WebFinger Resource
|
||||||
*
|
*
|
||||||
* @return string|WP_Error The URL or WP_Error
|
* @return string|WP_Error The URL or WP_Error
|
||||||
*/
|
*/
|
||||||
public static function resolve( $resource ) {
|
public static function resolve( $uri ) {
|
||||||
if ( ! $resource ) {
|
$data = self::get_data( $uri );
|
||||||
return null;
|
|
||||||
|
if ( \is_wp_error( $data ) ) {
|
||||||
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $resource, $m ) ) {
|
foreach ( $data['links'] as $link ) {
|
||||||
return null;
|
if (
|
||||||
}
|
'self' === $link['rel'] &&
|
||||||
|
'application/activity+json' === $link['type']
|
||||||
$transient_key = 'activitypub_resolve_' . ltrim( $resource, '@' );
|
) {
|
||||||
|
|
||||||
$link = \get_transient( $transient_key );
|
|
||||||
if ( $link ) {
|
|
||||||
return $link;
|
|
||||||
}
|
|
||||||
|
|
||||||
$url = \add_query_arg( 'resource', 'acct:' . ltrim( $resource, '@' ), 'https://' . $m[2] . '/.well-known/webfinger' );
|
|
||||||
if ( ! \wp_http_validate_url( $url ) ) {
|
|
||||||
$response = new WP_Error( 'invalid_webfinger_url', null, $url );
|
|
||||||
\set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to access author URL
|
|
||||||
$response = \wp_remote_get(
|
|
||||||
$url,
|
|
||||||
array(
|
|
||||||
'headers' => array( 'Accept' => 'application/jrd+json' ),
|
|
||||||
'redirection' => 2,
|
|
||||||
'timeout' => 2,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( \is_wp_error( $response ) ) {
|
|
||||||
$link = new WP_Error( 'webfinger_url_not_accessible', null, $url );
|
|
||||||
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
|
||||||
return $link;
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = \wp_remote_retrieve_body( $response );
|
|
||||||
$body = \json_decode( $body, true );
|
|
||||||
|
|
||||||
if ( empty( $body['links'] ) ) {
|
|
||||||
$link = new WP_Error( 'webfinger_url_invalid_response', null, $url );
|
|
||||||
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
|
||||||
return $link;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ( $body['links'] as $link ) {
|
|
||||||
if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) {
|
|
||||||
\set_transient( $transient_key, $link['href'], WEEK_IN_SECONDS );
|
|
||||||
return $link['href'];
|
return $link['href'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$link = new WP_Error( 'webfinger_url_no_activitypub', null, $body );
|
return new WP_Error( 'webfinger_url_no_activitypub', null, $data );
|
||||||
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
}
|
||||||
return $link;
|
|
||||||
|
/**
|
||||||
|
* Transform a URI to an acct <identifier>@<host>
|
||||||
|
*
|
||||||
|
* @param string $uri The URI (acct:, mailto:, http:, https:)
|
||||||
|
*
|
||||||
|
* @return string|WP_Error Error or acct URI
|
||||||
|
*/
|
||||||
|
public static function uri_to_acct( $uri ) {
|
||||||
|
$data = self::get_data( $uri );
|
||||||
|
|
||||||
|
if ( is_wp_error( $data ) ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if subject is an acct URI
|
||||||
|
if (
|
||||||
|
isset( $data['subject'] ) &&
|
||||||
|
\str_starts_with( $data['subject'], 'acct:' )
|
||||||
|
) {
|
||||||
|
return $data['subject'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// search for an acct URI in the aliases
|
||||||
|
if ( isset( $data['aliases'] ) ) {
|
||||||
|
foreach ( $data['aliases'] as $alias ) {
|
||||||
|
if ( \str_starts_with( $alias, 'acct:' ) ) {
|
||||||
|
return $alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_Error(
|
||||||
|
'webfinger_url_no_acct',
|
||||||
|
__( 'No acct URI found.', 'activitypub' ),
|
||||||
|
$data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,7 +134,7 @@ class Webfinger {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( empty( $host ) ) {
|
if ( empty( $host ) ) {
|
||||||
return new WP_Error( 'invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) );
|
return new WP_Error( 'webfinger_invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return array( $identifier, $host );
|
return array( $identifier, $host );
|
||||||
|
@ -146,55 +143,70 @@ class Webfinger {
|
||||||
/**
|
/**
|
||||||
* Get the WebFinger data for a given URI
|
* Get the WebFinger data for a given URI
|
||||||
*
|
*
|
||||||
* @param string $identifier The Identifier: <identifier>@<host>
|
* @param string $uri The Identifier: <identifier>@<host> or URI
|
||||||
* @param string $host The Host: <identifier>@<host>
|
|
||||||
*
|
*
|
||||||
* @return WP_Error|array Error reaction or array with
|
* @return WP_Error|array Error reaction or array with
|
||||||
* identifier and host as values
|
* identifier and host as values
|
||||||
*/
|
*/
|
||||||
public static function get_data( $identifier, $host ) {
|
public static function get_data( $uri ) {
|
||||||
$webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier );
|
|
||||||
|
|
||||||
$response = wp_safe_remote_get(
|
|
||||||
$webfinger_url,
|
|
||||||
array(
|
|
||||||
'headers' => array( 'Accept' => 'application/jrd+json' ),
|
|
||||||
'redirection' => 0,
|
|
||||||
'timeout' => 2,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( is_wp_error( $response ) ) {
|
|
||||||
return new WP_Error( 'webfinger_url_not_accessible', null, $webfinger_url );
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = wp_remote_retrieve_body( $response );
|
|
||||||
|
|
||||||
return json_decode( $body, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undocumented function
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function get_remote_follow_endpoint( $uri ) {
|
|
||||||
$identifier_and_host = self::get_identifier_and_host( $uri );
|
$identifier_and_host = self::get_identifier_and_host( $uri );
|
||||||
|
|
||||||
if ( is_wp_error( $identifier_and_host ) ) {
|
if ( is_wp_error( $identifier_and_host ) ) {
|
||||||
return $identifier_and_host;
|
return $identifier_and_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$transient_key = self::generate_cache_key( $uri );
|
||||||
|
|
||||||
list( $identifier, $host ) = $identifier_and_host;
|
list( $identifier, $host ) = $identifier_and_host;
|
||||||
|
|
||||||
$data = self::get_data( $identifier, $host );
|
$data = \get_transient( $transient_key );
|
||||||
|
if ( $data ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier );
|
||||||
|
|
||||||
|
$response = wp_safe_remote_get(
|
||||||
|
$webfinger_url,
|
||||||
|
array(
|
||||||
|
'headers' => array( 'Accept' => 'application/jrd+json' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return new WP_Error(
|
||||||
|
'webfinger_url_not_accessible',
|
||||||
|
__( 'The WebFinger Resource is not accessible.', 'activitypub' ),
|
||||||
|
$webfinger_url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = wp_remote_retrieve_body( $response );
|
||||||
|
$data = json_decode( $body, true );
|
||||||
|
|
||||||
|
\set_transient( $transient_key, $data, WEEK_IN_SECONDS );
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Remote-Follow endpoint for a given URI
|
||||||
|
*
|
||||||
|
* @return string|WP_Error Error or the Remote-Follow endpoint URI.
|
||||||
|
*/
|
||||||
|
public static function get_remote_follow_endpoint( $uri ) {
|
||||||
|
$data = self::get_data( $uri );
|
||||||
|
|
||||||
if ( is_wp_error( $data ) ) {
|
if ( is_wp_error( $data ) ) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( empty( $data['links'] ) ) {
|
if ( empty( $data['links'] ) ) {
|
||||||
return new WP_Error( 'webfinger_url_invalid_response', null, $data );
|
return new WP_Error(
|
||||||
|
'webfinger_missing_links',
|
||||||
|
__( 'No valid Link elements found.', 'activitypub' ),
|
||||||
|
$data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ( $data['links'] as $link ) {
|
foreach ( $data['links'] as $link ) {
|
||||||
|
@ -203,6 +215,27 @@ class Webfinger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WP_Error( 'webfinger_remote_follow_endpoint_invalid', $data, array( 'status' => 417 ) );
|
return new WP_Error(
|
||||||
|
'webfinger_missing_remote_follow_endpoint',
|
||||||
|
__( 'No valid Remote-Follow endpoint found.', 'activitypub' ),
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a cache key for a given URI
|
||||||
|
*
|
||||||
|
* @param string $uri A WebFinger Resource URI
|
||||||
|
*
|
||||||
|
* @return string The cache key
|
||||||
|
*/
|
||||||
|
public static function generate_cache_key( $uri ) {
|
||||||
|
$uri = ltrim( $uri, '@' );
|
||||||
|
|
||||||
|
if ( filter_var( $uri, FILTER_VALIDATE_EMAIL ) ) {
|
||||||
|
$uri = 'acct:' . $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'webfinger_' . md5( $uri );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,13 @@ class Interactions {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$in_reply_to = \esc_url_raw( $activity['object']['inReplyTo'] );
|
$in_reply_to = \esc_url_raw( $activity['object']['inReplyTo'] );
|
||||||
$comment_post_id = \url_to_postid( $in_reply_to );
|
$comment_post_id = \url_to_postid( $in_reply_to );
|
||||||
$parent_comment = object_id_to_comment( $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 ) {
|
if ( ! $comment_post_id && $parent_comment_id ) {
|
||||||
|
$parent_comment = get_comment( $parent_comment_id );
|
||||||
$comment_post_id = $parent_comment->comment_post_ID;
|
$comment_post_id = $parent_comment->comment_post_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,10 +59,9 @@ class Interactions {
|
||||||
'comment_content' => \addslashes( $activity['object']['content'] ),
|
'comment_content' => \addslashes( $activity['object']['content'] ),
|
||||||
'comment_type' => 'comment',
|
'comment_type' => 'comment',
|
||||||
'comment_author_email' => '',
|
'comment_author_email' => '',
|
||||||
'comment_parent' => $parent_comment ? $parent_comment->comment_ID : 0,
|
'comment_parent' => $parent_comment_id ? $parent_comment_id : 0,
|
||||||
'comment_meta' => array(
|
'comment_meta' => array(
|
||||||
'source_id' => \esc_url_raw( $activity['object']['id'] ),
|
'source_id' => \esc_url_raw( $activity['object']['id'] ),
|
||||||
'source_url' => \esc_url_raw( $activity['object']['url'] ),
|
|
||||||
'protocol' => 'activitypub',
|
'protocol' => 'activitypub',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -70,6 +70,10 @@ class Interactions {
|
||||||
$commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $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( $activity['object']['url'] );
|
||||||
|
}
|
||||||
|
|
||||||
// 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 );
|
||||||
// do not require email for AP entries
|
// do not require email for AP entries
|
||||||
|
@ -104,14 +108,14 @@ class Interactions {
|
||||||
$meta = get_remote_metadata_by_actor( $activity['actor'] );
|
$meta = get_remote_metadata_by_actor( $activity['actor'] );
|
||||||
|
|
||||||
//Determine comment_ID
|
//Determine comment_ID
|
||||||
$object_comment_id = url_to_commentid( \esc_url_raw( $activity['object']['id'] ) );
|
$comment = object_id_to_comment( \esc_url_raw( $activity['object']['id'] ) );
|
||||||
|
$commentdata = \get_comment( $comment, ARRAY_A );
|
||||||
|
|
||||||
if ( ! $object_comment_id ) {
|
if ( ! $commentdata ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//found a local 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_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'] ) ) {
|
if ( isset( $meta['icon']['url'] ) ) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Activitypub;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
use WP_Comment_Query;
|
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;
|
||||||
use Activitypub\Collection\Users;
|
use Activitypub\Collection\Users;
|
||||||
|
@ -168,6 +169,27 @@ function url_to_authorid( $url ) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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( 'c', null );
|
||||||
|
|
||||||
|
if ( ! is_null( $comment_id ) ) {
|
||||||
|
$comment = \get_comment( $comment_id );
|
||||||
|
|
||||||
|
// Only return local origin comments
|
||||||
|
if ( $comment && $comment->user_id ) {
|
||||||
|
return $comment_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for Tombstone Objects
|
* Check for Tombstone Objects
|
||||||
*
|
*
|
||||||
|
@ -579,7 +601,7 @@ function get_active_users( $duration = 1 ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$query = "SELECT COUNT( DISTINCT post_author ) FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish' AND post_date <= DATE_SUB( NOW(), INTERVAL %d MONTH )";
|
$query = "SELECT COUNT( DISTINCT post_author ) FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish' AND post_date <= DATE_SUB( NOW(), INTERVAL %d MONTH )";
|
||||||
$query = $wpdb->prepare( $query, $duration );
|
$query = $wpdb->prepare( $query, $duration );
|
||||||
$count = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
$count = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||||
|
|
||||||
set_transient( $transient_key, $count, DAY_IN_SECONDS );
|
set_transient( $transient_key, $count, DAY_IN_SECONDS );
|
||||||
}
|
}
|
||||||
|
@ -674,16 +696,33 @@ function url_to_commentid( $url ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for local comment
|
||||||
|
if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) === \wp_parse_url( $url, \PHP_URL_HOST ) ) {
|
||||||
|
$query = \wp_parse_url( $url, PHP_URL_QUERY );
|
||||||
|
|
||||||
|
if ( $query ) {
|
||||||
|
parse_str( $query, $params );
|
||||||
|
|
||||||
|
if ( ! empty( $params['c'] ) ) {
|
||||||
|
$comment = \get_comment( $params['c'] );
|
||||||
|
|
||||||
|
if ( $comment ) {
|
||||||
|
return $comment->comment_ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$args = array(
|
$args = array(
|
||||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||||
'meta_query' => array(
|
'meta_query' => array(
|
||||||
'relation' => 'OR',
|
'relation' => 'OR',
|
||||||
array(
|
array(
|
||||||
'key' => 'source_url',
|
'key' => 'source_url',
|
||||||
'value' => $url,
|
'value' => $url,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'key' => 'source_id',
|
'key' => 'source_id',
|
||||||
'value' => $url,
|
'value' => $url,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -50,14 +50,13 @@ class Users {
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
'callback' => array( self::class, 'remote_follow_get' ),
|
'callback' => array( self::class, 'remote_follow_get' ),
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
'args' => array(
|
'args' => array(
|
||||||
'resource' => array(
|
'resource' => array(
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'sanitize_callback' => 'sanitize_text_field',
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'permission_callback' => '__return_true',
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
49
includes/transformer/class-attachment.php
Normal file
49
includes/transformer/class-attachment.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
namespace Activitypub\Transformer;
|
||||||
|
|
||||||
|
use Activitypub\Transformer\Post;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WordPress Attachment Transformer
|
||||||
|
*
|
||||||
|
* The Attachment Transformer is responsible for transforming a WP_Post object into different other
|
||||||
|
* Object-Types.
|
||||||
|
*
|
||||||
|
* Currently supported are:
|
||||||
|
*
|
||||||
|
* - Activitypub\Activity\Base_Object
|
||||||
|
*/
|
||||||
|
class Attachment extends Post {
|
||||||
|
/**
|
||||||
|
* Generates all Media Attachments for a Post.
|
||||||
|
*
|
||||||
|
* @return array The Attachments.
|
||||||
|
*/
|
||||||
|
protected function get_attachment() {
|
||||||
|
$mime_type = get_post_mime_type( $this->object->ID );
|
||||||
|
$media_type = preg_replace( '/(\/[a-zA-Z]+)/i', '', $mime_type );
|
||||||
|
|
||||||
|
switch ( $media_type ) {
|
||||||
|
case 'audio':
|
||||||
|
case 'video':
|
||||||
|
$type = 'Document';
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
$type = 'Image';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$attachment = array(
|
||||||
|
'type' => $type,
|
||||||
|
'url' => wp_get_attachment_url( $this->object->ID ),
|
||||||
|
'mediaType' => $mime_type,
|
||||||
|
);
|
||||||
|
|
||||||
|
$alt = \get_post_meta( $this->object->ID, '_wp_attachment_image_alt', true );
|
||||||
|
if ( $alt ) {
|
||||||
|
$attachment['name'] = $alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attachment;
|
||||||
|
}
|
||||||
|
}
|
105
includes/transformer/class-base.php
Normal file
105
includes/transformer/class-base.php
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
<?php
|
||||||
|
namespace Activitypub\Transformer;
|
||||||
|
|
||||||
|
use Activitypub\Activity\Activity;
|
||||||
|
use Activitypub\Activity\Base_Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WordPress Base Transformer
|
||||||
|
*
|
||||||
|
* Transformers are responsible for transforming a WordPress objects into different ActivityPub
|
||||||
|
* Object-Types or Activities.
|
||||||
|
*/
|
||||||
|
abstract class Base {
|
||||||
|
/**
|
||||||
|
* The WP_Post object.
|
||||||
|
*
|
||||||
|
* @var
|
||||||
|
*/
|
||||||
|
protected $object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static function to Transform a WordPress Object.
|
||||||
|
*
|
||||||
|
* This helps to chain the output of the Transformer.
|
||||||
|
*
|
||||||
|
* @param stdClass $object The WP_Post object
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function transform( $object ) {
|
||||||
|
return new static( $object );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base constructor.
|
||||||
|
*
|
||||||
|
* @param stdClass $object
|
||||||
|
*/
|
||||||
|
public function __construct( $object ) {
|
||||||
|
$this->object = $object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the WordPress Object into an ActivityPub Object.
|
||||||
|
*
|
||||||
|
* @return Activitypub\Activity\Base_Object
|
||||||
|
*/
|
||||||
|
public function to_object() {
|
||||||
|
$object = new Base_Object();
|
||||||
|
|
||||||
|
$vars = $object->get_object_var_keys();
|
||||||
|
|
||||||
|
foreach ( $vars as $var ) {
|
||||||
|
$getter = 'get_' . $var;
|
||||||
|
|
||||||
|
if ( method_exists( $this, $getter ) ) {
|
||||||
|
$value = call_user_func( array( $this, $getter ) );
|
||||||
|
|
||||||
|
if ( isset( $value ) ) {
|
||||||
|
$setter = 'set_' . $var;
|
||||||
|
|
||||||
|
call_user_func( array( $object, $setter ), $value );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the ActivityPub Object to an Activity
|
||||||
|
*
|
||||||
|
* @param string $type The Activity-Type.
|
||||||
|
*
|
||||||
|
* @return \Activitypub\Activity\Activity The Activity.
|
||||||
|
*/
|
||||||
|
public function to_activity( $type ) {
|
||||||
|
$object = $this->to_object();
|
||||||
|
|
||||||
|
$activity = new Activity();
|
||||||
|
$activity->set_type( $type );
|
||||||
|
$activity->set_object( $object );
|
||||||
|
|
||||||
|
// Use simple Object (only ID-URI) for Like and Announce
|
||||||
|
if ( in_array( $type, array( 'Like', 'Announce' ), true ) ) {
|
||||||
|
$activity->set_object( $object->get_id() );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the WordPress Object.
|
||||||
|
*
|
||||||
|
* @return int The ID of the WordPress Object
|
||||||
|
*/
|
||||||
|
abstract public function get_wp_user_id();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the User-ID of the WordPress Post.
|
||||||
|
*
|
||||||
|
* @return int The User-ID of the WordPress Post
|
||||||
|
*/
|
||||||
|
abstract public function change_wp_user_id( $user_id );
|
||||||
|
}
|
277
includes/transformer/class-comment.php
Normal file
277
includes/transformer/class-comment.php
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
<?php
|
||||||
|
namespace Activitypub\Transformer;
|
||||||
|
|
||||||
|
use WP_Comment;
|
||||||
|
use WP_Comment_Query;
|
||||||
|
use Activitypub\Hashtag;
|
||||||
|
use Activitypub\Webfinger;
|
||||||
|
use Activitypub\Model\Blog_User;
|
||||||
|
use Activitypub\Collection\Users;
|
||||||
|
use Activitypub\Transformer\Base;
|
||||||
|
use Activitypub\Activity\Base_Object;
|
||||||
|
|
||||||
|
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 extends Base {
|
||||||
|
/**
|
||||||
|
* Returns the User-ID of the WordPress Comment.
|
||||||
|
*
|
||||||
|
* @return int The User-ID of the WordPress Comment
|
||||||
|
*/
|
||||||
|
public function get_wp_user_id() {
|
||||||
|
return $this->object->user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the User-ID of the WordPress Comment.
|
||||||
|
*
|
||||||
|
* @return int The User-ID of the WordPress Comment
|
||||||
|
*/
|
||||||
|
public function change_wp_user_id( $user_id ) {
|
||||||
|
$this->object->user_id = $user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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->object;
|
||||||
|
$object = parent::to_object();
|
||||||
|
|
||||||
|
$object->set_url( \get_comment_link( $comment->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_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 ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
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->object->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->object;
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the in-reply-to for the ActivityPub Item.
|
||||||
|
*
|
||||||
|
* @return int The URL of the in-reply-to.
|
||||||
|
*/
|
||||||
|
protected function get_in_reply_to() {
|
||||||
|
$comment = $this->object;
|
||||||
|
|
||||||
|
$parent_comment = \get_comment( $comment->comment_parent );
|
||||||
|
|
||||||
|
if ( $parent_comment ) {
|
||||||
|
$comment_meta = \get_comment_meta( $parent_comment->comment_ID );
|
||||||
|
|
||||||
|
if ( ! empty( $comment_meta['source_id'][0] ) ) {
|
||||||
|
$in_reply_to = $comment_meta['source_id'][0];
|
||||||
|
} elseif ( ! empty( $comment_meta['source_url'][0] ) ) {
|
||||||
|
$in_reply_to = $comment_meta['source_url'][0];
|
||||||
|
} else {
|
||||||
|
$in_reply_to = $this->generate_id( $parent_comment );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$in_reply_to = \get_permalink( $comment->comment_post_ID );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $in_reply_to;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the ActivityPub Object.
|
||||||
|
*
|
||||||
|
* @see https://www.w3.org/TR/activitypub/#obj-id
|
||||||
|
* @see https://github.com/tootsuite/mastodon/issues/13879
|
||||||
|
*
|
||||||
|
* @return string ActivityPub URI for comment
|
||||||
|
*/
|
||||||
|
protected function get_id() {
|
||||||
|
$comment = $this->object;
|
||||||
|
return $this->generate_id( $comment );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an ActivityPub URI for a comment
|
||||||
|
*
|
||||||
|
* @param WP_Comment|int $comment A comment object or comment ID
|
||||||
|
*
|
||||||
|
* @return string ActivityPub URI for comment
|
||||||
|
*/
|
||||||
|
protected function generate_id( $comment ) {
|
||||||
|
$comment = get_comment( $comment );
|
||||||
|
|
||||||
|
return \add_query_arg(
|
||||||
|
array(
|
||||||
|
'c' => $comment->comment_ID,
|
||||||
|
),
|
||||||
|
\trailingslashit( site_url() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment_query = new WP_Comment_Query(
|
||||||
|
array(
|
||||||
|
'post_id' => $this->object->comment_post_ID,
|
||||||
|
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||||
|
'meta_query' => array(
|
||||||
|
array(
|
||||||
|
'key' => 'source_id',
|
||||||
|
'compare' => 'EXISTS',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $comment_query->comments ) {
|
||||||
|
foreach ( $comment_query->comments as $comment ) {
|
||||||
|
if ( empty( $comment->comment_author_url ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$cc[] = \esc_url( $comment->comment_author_url );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cc = \array_unique( $cc );
|
||||||
|
|
||||||
|
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_tag() {
|
||||||
|
$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 \array_unique( $tags, SORT_REGULAR );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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->object->comment_content, $this->object );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the locale of the post.
|
||||||
|
*
|
||||||
|
* @return string The locale of the post.
|
||||||
|
*/
|
||||||
|
public function get_locale() {
|
||||||
|
$comment_id = $this->object->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->object );
|
||||||
|
}
|
||||||
|
}
|
61
includes/transformer/class-factory.php
Normal file
61
includes/transformer/class-factory.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
namespace Activitypub\Transformer;
|
||||||
|
|
||||||
|
use Activitypub\Transformer\Post;
|
||||||
|
use Activitypub\Transformer\Comment;
|
||||||
|
use Activitypub\Transformer\Attachment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformer Factory
|
||||||
|
*/
|
||||||
|
class Factory {
|
||||||
|
public static function get_transformer( $object ) {
|
||||||
|
/**
|
||||||
|
* Filter the transformer for a given object.
|
||||||
|
*
|
||||||
|
* Add your own transformer based on the object class or the object type.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* // Filter be object class
|
||||||
|
* add_filter( 'activitypub_transformer', function( $transformer, $object, $object_class ) {
|
||||||
|
* if ( $object_class === 'WP_Post' ) {
|
||||||
|
* return new My_Post_Transformer( $object );
|
||||||
|
* }
|
||||||
|
* return $transformer;
|
||||||
|
* }, 10, 3 );
|
||||||
|
*
|
||||||
|
* // Filter be object type
|
||||||
|
* add_filter( 'activitypub_transformer', function( $transformer, $object, $object_class ) {
|
||||||
|
* if ( $object->post_type === 'event' ) {
|
||||||
|
* return new My_Event_Transformer( $object );
|
||||||
|
* }
|
||||||
|
* return $transformer;
|
||||||
|
* }, 10, 3 );
|
||||||
|
*
|
||||||
|
* @param Activitypub\Transformer\Base $transformer The transformer to use.
|
||||||
|
* @param mixed $object The object to transform.
|
||||||
|
* @param string $object_class The class of the object to transform.
|
||||||
|
*
|
||||||
|
* @return mixed The transformer to use.
|
||||||
|
*/
|
||||||
|
$transformer = apply_filters( 'activitypub_transformer', null, $object, get_class( $object ) );
|
||||||
|
|
||||||
|
if ( $transformer ) {
|
||||||
|
return $transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use default transformer
|
||||||
|
switch ( get_class( $object ) ) {
|
||||||
|
case 'WP_Post':
|
||||||
|
if ( 'attachment' === $object->post_type ) {
|
||||||
|
return new Attachment( $object );
|
||||||
|
}
|
||||||
|
return new Post( $object );
|
||||||
|
case 'WP_Comment':
|
||||||
|
return new Comment( $object );
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,11 @@
|
||||||
namespace Activitypub\Transformer;
|
namespace Activitypub\Transformer;
|
||||||
|
|
||||||
use WP_Post;
|
use WP_Post;
|
||||||
use Activitypub\Collection\Users;
|
|
||||||
use Activitypub\Model\Blog_User;
|
|
||||||
use Activitypub\Activity\Base_Object;
|
|
||||||
use Activitypub\Shortcodes;
|
use Activitypub\Shortcodes;
|
||||||
|
use Activitypub\Model\Blog_User;
|
||||||
|
use Activitypub\Transformer\Base;
|
||||||
|
use Activitypub\Collection\Users;
|
||||||
|
use Activitypub\Activity\Base_Object;
|
||||||
|
|
||||||
use function Activitypub\esc_hashtag;
|
use function Activitypub\esc_hashtag;
|
||||||
use function Activitypub\is_single_user;
|
use function Activitypub\is_single_user;
|
||||||
|
@ -15,42 +16,32 @@ use function Activitypub\site_supports_blocks;
|
||||||
/**
|
/**
|
||||||
* WordPress Post Transformer
|
* WordPress Post Transformer
|
||||||
*
|
*
|
||||||
* The Post Transformer is responsible for transforming a WP_Post object into different othe
|
* The Post Transformer is responsible for transforming a WP_Post object into different other
|
||||||
* Object-Types.
|
* Object-Types.
|
||||||
*
|
*
|
||||||
* Currently supported are:
|
* Currently supported are:
|
||||||
*
|
*
|
||||||
* - Activitypub\Activity\Base_Object
|
* - Activitypub\Activity\Base_Object
|
||||||
*/
|
*/
|
||||||
class Post {
|
class Post extends Base {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The WP_Post object.
|
* Returns the ID of the WordPress Post.
|
||||||
*
|
*
|
||||||
* @var WP_Post
|
* @return int The ID of the WordPress Post
|
||||||
*/
|
*/
|
||||||
protected $wp_post;
|
public function get_wp_user_id() {
|
||||||
|
return $this->object->post_author;
|
||||||
/**
|
|
||||||
* Static function to Transform a WP_Post Object.
|
|
||||||
*
|
|
||||||
* This helps to chain the output of the Transformer.
|
|
||||||
*
|
|
||||||
* @param WP_Post $wp_post The WP_Post object
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function transform( WP_Post $wp_post ) {
|
|
||||||
return new static( $wp_post );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Change the User-ID of the WordPress Post.
|
||||||
*
|
*
|
||||||
*
|
* @return int The User-ID of the WordPress Post
|
||||||
* @param WP_Post $wp_post
|
|
||||||
*/
|
*/
|
||||||
public function __construct( WP_Post $wp_post ) {
|
public function change_wp_user_id( $user_id ) {
|
||||||
$this->wp_post = $wp_post;
|
$this->object->post_author = $user_id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,31 +52,25 @@ class Post {
|
||||||
* @return \Activitypub\Activity\Base_Object The ActivityPub Object
|
* @return \Activitypub\Activity\Base_Object The ActivityPub Object
|
||||||
*/
|
*/
|
||||||
public function to_object() {
|
public function to_object() {
|
||||||
$wp_post = $this->wp_post;
|
$post = $this->object;
|
||||||
$object = new Base_Object();
|
$object = parent::to_object();
|
||||||
|
|
||||||
$object->set_id( $this->get_id() );
|
$published = \strtotime( $post->post_date_gmt );
|
||||||
$object->set_url( $this->get_url() );
|
|
||||||
$object->set_type( $this->get_object_type() );
|
|
||||||
|
|
||||||
$published = \strtotime( $wp_post->post_date_gmt );
|
|
||||||
|
|
||||||
$object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
|
$object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
|
||||||
|
|
||||||
$updated = \strtotime( $wp_post->post_modified_gmt );
|
$updated = \strtotime( $post->post_modified_gmt );
|
||||||
|
|
||||||
if ( $updated > $published ) {
|
if ( $updated > $published ) {
|
||||||
$object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
|
$object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$object->set_attributed_to( $this->get_attributed_to() );
|
|
||||||
$object->set_content( $this->get_content() );
|
|
||||||
$object->set_content_map(
|
$object->set_content_map(
|
||||||
array(
|
array(
|
||||||
$this->get_locale() => $this->get_content(),
|
$this->get_locale() => $this->get_content(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$path = sprintf( 'users/%d/followers', intval( $wp_post->post_author ) );
|
$path = sprintf( 'users/%d/followers', intval( $post->post_author ) );
|
||||||
|
|
||||||
$object->set_to(
|
$object->set_to(
|
||||||
array(
|
array(
|
||||||
|
@ -93,9 +78,6 @@ class Post {
|
||||||
get_rest_url_by_path( $path ),
|
get_rest_url_by_path( $path ),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$object->set_cc( $this->get_cc() );
|
|
||||||
$object->set_attachment( $this->get_attachments() );
|
|
||||||
$object->set_tag( $this->get_tags() );
|
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
|
@ -115,7 +97,7 @@ class Post {
|
||||||
* @return string The Posts URL.
|
* @return string The Posts URL.
|
||||||
*/
|
*/
|
||||||
public function get_url() {
|
public function get_url() {
|
||||||
$post = $this->wp_post;
|
$post = $this->object;
|
||||||
|
|
||||||
if ( 'trash' === get_post_status( $post ) ) {
|
if ( 'trash' === get_post_status( $post ) ) {
|
||||||
$permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
|
$permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
|
||||||
|
@ -139,7 +121,7 @@ class Post {
|
||||||
return $user->get_url();
|
return $user->get_url();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Users::get_by_id( $this->wp_post->post_author )->get_url();
|
return Users::get_by_id( $this->object->post_author )->get_url();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,12 +129,12 @@ class Post {
|
||||||
*
|
*
|
||||||
* @return array The Attachments.
|
* @return array The Attachments.
|
||||||
*/
|
*/
|
||||||
protected function get_attachments() {
|
protected function get_attachment() {
|
||||||
// Once upon a time we only supported images, but we now support audio/video as well.
|
// Once upon a time we only supported images, but we now support audio/video as well.
|
||||||
// We maintain the image-centric naming for backwards compatibility.
|
// We maintain the image-centric naming for backwards compatibility.
|
||||||
$max_media = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) );
|
$max_media = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) );
|
||||||
|
|
||||||
if ( site_supports_blocks() && \has_blocks( $this->wp_post->post_content ) ) {
|
if ( site_supports_blocks() && \has_blocks( $this->object->post_content ) ) {
|
||||||
return $this->get_block_attachments( $max_media );
|
return $this->get_block_attachments( $max_media );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +154,7 @@ class Post {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = $this->wp_post->ID;
|
$id = $this->object->ID;
|
||||||
|
|
||||||
$media_ids = array();
|
$media_ids = array();
|
||||||
|
|
||||||
|
@ -182,7 +164,7 @@ class Post {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $max_media > 0 ) {
|
if ( $max_media > 0 ) {
|
||||||
$blocks = \parse_blocks( $this->wp_post->post_content );
|
$blocks = \parse_blocks( $this->object->post_content );
|
||||||
$media_ids = self::get_media_ids_from_blocks( $blocks, $media_ids, $max_media );
|
$media_ids = self::get_media_ids_from_blocks( $blocks, $media_ids, $max_media );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +185,7 @@ class Post {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = $this->wp_post->ID;
|
$id = $this->object->ID;
|
||||||
|
|
||||||
$image_ids = array();
|
$image_ids = array();
|
||||||
|
|
||||||
|
@ -316,7 +298,7 @@ class Post {
|
||||||
*/
|
*/
|
||||||
$thumbnail = apply_filters(
|
$thumbnail = apply_filters(
|
||||||
'activitypub_get_image',
|
'activitypub_get_image',
|
||||||
self::get_image( $id, $image_size ),
|
self::get_wordpress_attachment( $id, $image_size ),
|
||||||
$id,
|
$id,
|
||||||
$image_size
|
$image_size
|
||||||
);
|
);
|
||||||
|
@ -365,7 +347,7 @@ class Post {
|
||||||
*
|
*
|
||||||
* @return array|false Array of image data, or boolean false if no image is available.
|
* @return array|false Array of image data, or boolean false if no image is available.
|
||||||
*/
|
*/
|
||||||
protected static function get_image( $id, $image_size = 'full' ) {
|
protected static function get_wordpress_attachment( $id, $image_size = 'full' ) {
|
||||||
/**
|
/**
|
||||||
* Hook into the image retrieval process. Before image retrieval.
|
* Hook into the image retrieval process. Before image retrieval.
|
||||||
*
|
*
|
||||||
|
@ -402,10 +384,10 @@ class Post {
|
||||||
|
|
||||||
// Default to Article.
|
// Default to Article.
|
||||||
$object_type = 'Article';
|
$object_type = 'Article';
|
||||||
$post_type = \get_post_type( $this->wp_post );
|
$post_type = \get_post_type( $this->object );
|
||||||
switch ( $post_type ) {
|
switch ( $post_type ) {
|
||||||
case 'post':
|
case 'post':
|
||||||
$post_format = \get_post_format( $this->wp_post );
|
$post_format = \get_post_format( $this->object );
|
||||||
switch ( $post_format ) {
|
switch ( $post_format ) {
|
||||||
case 'aside':
|
case 'aside':
|
||||||
case 'status':
|
case 'status':
|
||||||
|
@ -481,10 +463,10 @@ class Post {
|
||||||
*
|
*
|
||||||
* @return array The list of Tags.
|
* @return array The list of Tags.
|
||||||
*/
|
*/
|
||||||
protected function get_tags() {
|
protected function get_tag() {
|
||||||
$tags = array();
|
$tags = array();
|
||||||
|
|
||||||
$post_tags = \get_the_tags( $this->wp_post->ID );
|
$post_tags = \get_the_tags( $this->object->ID );
|
||||||
if ( $post_tags ) {
|
if ( $post_tags ) {
|
||||||
foreach ( $post_tags as $post_tag ) {
|
foreach ( $post_tags as $post_tag ) {
|
||||||
$tag = array(
|
$tag = array(
|
||||||
|
@ -531,7 +513,7 @@ class Post {
|
||||||
do_action( 'activitypub_before_get_content', $post );
|
do_action( 'activitypub_before_get_content', $post );
|
||||||
|
|
||||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||||
$post = $this->wp_post;
|
$post = $this->object;
|
||||||
$content = $this->get_post_content_template();
|
$content = $this->get_post_content_template();
|
||||||
|
|
||||||
// Register our shortcodes just in time.
|
// Register our shortcodes just in time.
|
||||||
|
@ -576,7 +558,7 @@ class Post {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply_filters( 'activitypub_object_content_template', $template, $this->wp_post );
|
return apply_filters( 'activitypub_object_content_template', $template, $this->object );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -585,7 +567,7 @@ class Post {
|
||||||
* @return array The list of @-Mentions.
|
* @return array The list of @-Mentions.
|
||||||
*/
|
*/
|
||||||
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->object->post_content, $this->object );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -594,7 +576,7 @@ class Post {
|
||||||
* @return string The locale of the post.
|
* @return string The locale of the post.
|
||||||
*/
|
*/
|
||||||
public function get_locale() {
|
public function get_locale() {
|
||||||
$post_id = $this->wp_post->ID;
|
$post_id = $this->object->ID;
|
||||||
$lang = \strtolower( \strtok( \get_locale(), '_-' ) );
|
$lang = \strtolower( \strtok( \get_locale(), '_-' ) );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -606,6 +588,6 @@ class Post {
|
||||||
*
|
*
|
||||||
* @return string The filtered locale of the post.
|
* @return string The filtered locale of the post.
|
||||||
*/
|
*/
|
||||||
return apply_filters( 'activitypub_post_locale', $lang, $post_id, $this->wp_post );
|
return apply_filters( 'activitypub_post_locale', $lang, $post_id, $this->object );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu
|
||||||
* Added: Make Post-Template filterable
|
* Added: Make Post-Template filterable
|
||||||
* Added: CSS class for ActivityPub comments to allow custom designs
|
* Added: CSS class for ActivityPub comments to allow custom designs
|
||||||
* Added: FEP-2677: Identifying the Application Actor
|
* Added: FEP-2677: Identifying the Application Actor
|
||||||
|
* Added: Basic Comment Federation
|
||||||
* Improved: WebFinger endpoints
|
* Improved: WebFinger endpoints
|
||||||
|
|
||||||
= 1.3.0 =
|
= 1.3.0 =
|
||||||
|
|
|
@ -10,23 +10,8 @@ $user->set_context(
|
||||||
*/
|
*/
|
||||||
\do_action( 'activitypub_json_author_pre', $user->get__id() );
|
\do_action( 'activitypub_json_author_pre', $user->get__id() );
|
||||||
|
|
||||||
$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_author_options', $options, $user->get__id() );
|
|
||||||
|
|
||||||
\header( 'Content-Type: application/activity+json' );
|
\header( 'Content-Type: application/activity+json' );
|
||||||
echo \wp_json_encode( $user->to_array(), $options );
|
echo $user->to_json(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
||||||
|
|
|
@ -10,23 +10,8 @@ $user->set_context(
|
||||||
*/
|
*/
|
||||||
\do_action( 'activitypub_json_author_pre', $user->get__id() );
|
\do_action( 'activitypub_json_author_pre', $user->get__id() );
|
||||||
|
|
||||||
$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_author_options', $options, $user->get__id() );
|
|
||||||
|
|
||||||
\header( 'Content-Type: application/activity+json' );
|
\header( 'Content-Type: application/activity+json' );
|
||||||
echo \wp_json_encode( $user->to_array(), $options );
|
echo $user->to_json(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
||||||
|
|
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( 'c', null ) ); // phpcs:ignore
|
||||||
|
|
||||||
|
$object = \Activitypub\Transformer\Factory::get_transformer( $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' );
|
|
@ -2,34 +2,16 @@
|
||||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||||
$post = \get_post();
|
$post = \get_post();
|
||||||
|
|
||||||
$object = new \Activitypub\Transformer\Post( $post );
|
$post_object = \Activitypub\Transformer\Factory::get_transformer( $post )->to_object();
|
||||||
$json = \array_merge( array( '@context' => \Activitypub\get_context() ), $object->to_object()->to_array() );
|
$post_object->set_context( \Activitypub\get_context() );
|
||||||
|
|
||||||
// filter output
|
|
||||||
$json = \apply_filters( 'activitypub_json_post_array', $json );
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
* Action triggerd prior to the ActivityPub profile being created and sent to the client
|
||||||
*/
|
*/
|
||||||
\do_action( 'activitypub_json_post_pre' );
|
\do_action( 'activitypub_json_post_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_post_options', $options );
|
|
||||||
|
|
||||||
\header( 'Content-Type: application/activity+json' );
|
\header( 'Content-Type: application/activity+json' );
|
||||||
echo \wp_json_encode( $json, $options );
|
echo $post_object->to_json(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
* Action triggerd after the ActivityPub profile has been created and sent to the client
|
||||||
|
|
21
tests/test-class-activitypub-webfinger.php
Normal file
21
tests/test-class-activitypub-webfinger.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
class Test_Activitypub_Webfinger extends WP_UnitTestCase {
|
||||||
|
/**
|
||||||
|
* @dataProvider the_cache_key_provider
|
||||||
|
*/
|
||||||
|
public function test_generate_cache_key( $uri, $hash ) {
|
||||||
|
$cache_key = Activitypub\Webfinger::generate_cache_key( $uri );
|
||||||
|
|
||||||
|
$this->assertEquals( $cache_key, 'webfinger_' . $hash );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function the_cache_key_provider() {
|
||||||
|
return array(
|
||||||
|
array( 'http://example.org/?author=1', md5( 'http://example.org/?author=1' ) ),
|
||||||
|
array( '@author@example.org', md5( 'acct:author@example.org' ) ),
|
||||||
|
array( 'author@example.org', md5( 'acct:author@example.org' ) ),
|
||||||
|
array( 'acct:author@example.org', md5( 'acct:author@example.org' ) ),
|
||||||
|
array( 'https://example.org', md5( 'https://example.org' ) ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue