updated plugin ActivityPub version 3.3.3

This commit is contained in:
2024-10-09 12:44:17 +00:00
committed by Gitium
parent fb4b27bbc6
commit c54fa007bd
106 changed files with 7070 additions and 2918 deletions

View File

@ -1,36 +1,50 @@
<?php
/**
* BuddyPress integration class file.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
/**
* Compatibility with the BuddyPress plugin
* Compatibility with the BuddyPress plugin.
*
* @see https://buddypress.org/
*/
class Buddypress {
/**
* Initialize the class, registering WordPress hooks
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_filter( 'activitypub_json_author_array', array( self::class, 'add_user_metadata' ), 11, 2 );
}
public static function add_user_metadata( $object, $author_id ) {
$object->url = bp_core_get_user_domain( $author_id ); //add BP member profile URL as user URL
/**
* Add BuddyPress user metadata to the author array.
*
* @param object $author The author object.
* @param int $author_id The author ID.
*
* @return object The author object.
*/
public static function add_user_metadata( $author, $author_id ) {
$author->url = bp_core_get_user_domain( $author_id ); // Add BP member profile URL as user URL.
// add BuddyPress' cover_image instead of WordPress' header_image
// Add BuddyPress' cover_image instead of WordPress' header_image.
$cover_image_url = bp_attachments_get_attachment( 'url', array( 'item_id' => $author_id ) );
if ( $cover_image_url ) {
$object->image = array(
$author->image = array(
'type' => 'Image',
'url' => $cover_image_url,
);
}
// change profile URL to BuddyPress' profile URL
$object->attachment['profile_url'] = array(
'type' => 'PropertyValue',
'name' => \__( 'Profile', 'activitypub' ),
// Change profile URL to BuddyPress' profile URL.
$author->attachment['profile_url'] = array(
'type' => 'PropertyValue',
'name' => \__( 'Profile', 'activitypub' ),
'value' => \html_entity_decode(
sprintf(
'<a rel="me" title="%s" target="_blank" href="%s">%s</a>',
@ -43,18 +57,18 @@ class Buddypress {
),
);
// replace blog URL on multisite
// Replace blog URL on multisite.
if ( is_multisite() ) {
$user_blogs = get_blogs_of_user( $author_id ); //get sites of user to send as AP metadata
$user_blogs = get_blogs_of_user( $author_id ); // Get sites of user to send as AP metadata.
if ( ! empty( $user_blogs ) ) {
unset( $object->attachment['blog_url'] );
unset( $author->attachment['blog_url'] );
foreach ( $user_blogs as $blog ) {
if ( 1 !== $blog->userblog_id ) {
$object->attachment[] = array(
'type' => 'PropertyValue',
'name' => $blog->blogname,
$author->attachment[] = array(
'type' => 'PropertyValue',
'name' => $blog->blogname,
'value' => \html_entity_decode(
sprintf(
'<a rel="me" title="%s" target="_blank" href="%s">%s</a>',
@ -71,6 +85,6 @@ class Buddypress {
}
}
return $object;
return $author;
}
}

View File

@ -1,4 +1,10 @@
<?php
/**
* Enable Mastodon Apps integration class file.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
use DateTime;
@ -6,46 +12,176 @@ use Activitypub\Webfinger as Webfinger_Util;
use Activitypub\Http;
use Activitypub\Collection\Users;
use Activitypub\Collection\Followers;
use Activitypub\Integration\Nodeinfo;
use Activitypub\Collection\Extra_Fields;
use Enable_Mastodon_Apps\Mastodon_API;
use Enable_Mastodon_Apps\Entity\Account;
use Enable_Mastodon_Apps\Entity\Status;
use Enable_Mastodon_Apps\Entity\Media_Attachment;
use function Activitypub\get_remote_metadata_by_actor;
use function Activitypub\is_user_type_disabled;
/**
* Class Enable_Mastodon_Apps
* Class Enable_Mastodon_Apps.
*
* This class is used to enable Mastodon Apps to work with ActivityPub
* This class is used to enable Mastodon Apps to work with ActivityPub.
*
* @see https://github.com/akirk/enable-mastodon-apps
*/
class Enable_Mastodon_Apps {
/**
* Initialize the class, registering WordPress hooks
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_filter( 'mastodon_api_account_followers', array( self::class, 'api_account_followers' ), 10, 2 );
\add_filter( 'mastodon_api_account', array( self::class, 'api_account_add_followers' ), 20, 2 );
\add_filter( 'mastodon_api_account', array( self::class, 'api_account_external' ), 15, 2 );
\add_filter( 'mastodon_api_account', array( self::class, 'api_account_internal' ), 9, 2 );
\add_filter( 'mastodon_api_search', array( self::class, 'api_search' ), 40, 2 );
\add_filter( 'mastodon_api_search', array( self::class, 'api_search_by_url' ), 40, 2 );
\add_filter( 'mastodon_api_get_posts_query_args', array( self::class, 'api_get_posts_query_args' ) );
\add_filter( 'mastodon_api_statuses', array( self::class, 'api_statuses_external' ), 10, 2 );
\add_filter( 'mastodon_api_status_context', array( self::class, 'api_get_replies' ), 10, 23 );
\add_action( 'mastodon_api_update_credentials', array( self::class, 'api_update_credentials' ), 10, 2 );
}
/**
* Add followers to Mastodon API
* Map user to blog if user is disabled.
*
* @param array $followers An array of followers
* @param string $user_id The user id
* @param WP_REST_Request $request The request object
* @param int $user_id The user id.
*
* @return int The user id.
*/
public static function maybe_map_user_to_blog( $user_id ) {
if (
is_user_type_disabled( 'user' ) &&
! is_user_type_disabled( 'blog' ) &&
// Check if the blog user is permissible for this user.
user_can( $user_id, 'activitypub' )
) {
return Users::BLOG_USER_ID;
}
return $user_id;
}
/**
* Update profile data for Mastodon API.
*
* @param array $data The data to act on.
* @param int $user_id The user id.
* @return array The possibly-filtered data (data that's saved gets unset from the array).
*/
public static function api_update_credentials( $data, $user_id ) {
if ( empty( $user_id ) ) {
return $data;
}
$user_id = self::maybe_map_user_to_blog( $user_id );
$user = Users::get_by_id( $user_id );
if ( ! $user || is_wp_error( $user ) ) {
return $data;
}
// User::update_icon and other update_* methods check data validity, so we don't need to do it here.
if ( isset( $data['avatar'] ) && $user->update_icon( $data['avatar'] ) ) {
// Unset the avatar so it doesn't get saved again by other plugins.
// Ditto for all other fields below.
unset( $data['avatar'] );
}
if ( isset( $data['header'] ) && $user->update_header( $data['header'] ) ) {
unset( $data['header'] );
}
if ( isset( $data['display_name'] ) && $user->update_name( $data['display_name'] ) ) {
unset( $data['display_name'] );
}
if ( isset( $data['note'] ) && $user->update_summary( $data['note'] ) ) {
unset( $data['note'] );
}
if ( isset( $data['fields_attributes'] ) ) {
self::set_extra_fields( $user_id, $data['fields_attributes'] );
unset( $data['fields_attributes'] );
}
return $data;
}
/**
* Get extra fields for Mastodon API.
*
* @param int $user_id The user id to act on.
* @return array The extra fields.
*/
private static function get_extra_fields( $user_id ) {
$ret = array();
$fields = Extra_Fields::get_actor_fields( $user_id );
foreach ( $fields as $field ) {
$ret[] = array(
'name' => $field->post_title,
'value' => Extra_Fields::get_formatted_content( $field ),
);
}
return $ret;
}
/**
* Set extra fields for Mastodon API.
*
* @param int $user_id The user id to act on.
* @param array $fields The fields to set. It is assumed to be the entire set of desired fields.
*/
private static function set_extra_fields( $user_id, $fields ) {
// The Mastodon API submits a simple hash for every field.
// We can reasonably assume a similar order for our operations below.
$ids = wp_list_pluck( Extra_Fields::get_actor_fields( $user_id ), 'ID' );
$is_blog = Users::BLOG_USER_ID === $user_id;
$post_type = $is_blog ? Extra_Fields::BLOG_POST_TYPE : Extra_Fields::USER_POST_TYPE;
foreach ( $fields as $i => $field ) {
$post_id = $ids[ $i ] ?? null;
$has_post = $post_id && \get_post( $post_id );
$args = array(
'post_title' => $field['name'],
'post_content' => Extra_Fields::make_paragraph_block( $field['value'] ),
);
if ( $has_post ) {
$args['ID'] = $ids[ $i ];
\wp_update_post( $args );
} else {
$args['post_type'] = $post_type;
$args['post_status'] = 'publish';
if ( ! $is_blog ) {
$args['post_author'] = $user_id;
}
\wp_insert_post( $args );
}
}
// Delete any remaining fields.
if ( \count( $fields ) < \count( $ids ) ) {
$to_delete = \array_slice( $ids, \count( $fields ) );
foreach ( $to_delete as $id ) {
\wp_delete_post( $id, true );
}
}
}
/**
* Add followers to Mastodon API.
*
* @param array $followers An array of followers.
* @param string $user_id The user id.
*
* @return array The filtered followers
*/
public static function api_account_followers( $followers, $user_id ) {
$user_id = self::maybe_map_user_to_blog( $user_id );
$activitypub_followers = Followers::get_followers( $user_id, 40 );
$mastodon_followers = array_map(
function ( $item ) {
@ -57,92 +193,45 @@ class Enable_Mastodon_Apps {
$acct = $item->get_url();
}
$account = new Account();
$account->id = \strval( $item->get__id() );
$account->username = $item->get_preferred_username();
$account->acct = $acct;
$account->display_name = $item->get_name();
$account->url = $item->get_url();
$account->uri = $item->get_id();
$account->avatar = $item->get_icon_url();
$account->avatar_static = $item->get_icon_url();
$account->created_at = new DateTime( $item->get_published() );
$account->last_status_at = new DateTime( $item->get_published() );
$account->note = $item->get_summary();
$account->header = $item->get_image_url();
$account->header_static = $item->get_image_url();
$account = new Account();
$account->id = \strval( $item->get__id() );
$account->username = $item->get_preferred_username();
$account->acct = $acct;
$account->display_name = $item->get_name();
$account->url = $item->get_url();
$account->avatar = $item->get_icon_url();
$account->avatar_static = $item->get_icon_url();
$account->created_at = new DateTime( $item->get_published() );
$account->last_status_at = new DateTime( $item->get_published() );
$account->note = $item->get_summary();
$account->header = $item->get_image_url();
$account->header_static = $item->get_image_url();
$account->followers_count = 0;
$account->following_count = 0;
$account->statuses_count = 0;
$account->bot = false;
$account->locked = false;
$account->group = false;
$account->discoversable = false;
$account->indexable = false;
$account->hide_collections = false;
$account->noindex = false;
$account->fields = array();
$account->emojis = array();
$account->roles = array();
$account->statuses_count = 0;
$account->bot = false;
$account->locked = false;
$account->group = false;
$account->discoverable = false;
$account->noindex = false;
$account->fields = array();
$account->emojis = array();
return $account;
},
$activitypub_followers
);
$followers = array_merge( $mastodon_followers, $followers );
return $followers;
}
/**
* Add followers count to Mastodon API
*
* @param Enable_Mastodon_Apps\Entity\Account $account The account
* @param int $user_id The user id
*
* @return Enable_Mastodon_Apps\Entity\Account The filtered Account
*/
public static function api_account_add_followers( $account, $user_id ) {
if ( ! $account instanceof Account ) {
return $account;
}
$user = Users::get_by_various( $user_id );
if ( ! $user || is_wp_error( $user ) ) {
return $account;
}
$header = $user->get_image();
if ( $header ) {
$account->header = $header['url'];
$account->header_static = $header['url'];
}
foreach ( $user->get_attachment() as $attachment ) {
if ( 'PropertyValue' === $attachment['type'] ) {
$account->fields[] = array(
'name' => $attachment['name'],
'value' => $attachment['value'],
);
}
}
$account->acct = $user->get_preferred_username();
$account->note = $user->get_summary();
$account->followers_count = Followers::count_followers( $user->get__id() );
return $account;
return array_merge( $mastodon_followers, $followers );
}
/**
* Resolve external accounts for Mastodon API
*
* @param Enable_Mastodon_Apps\Entity\Account $user_data The user data
* @param string $user_id The user id
* @param Account $user_data The user data.
* @param string $user_id The user id.
*
* @return Enable_Mastodon_Apps\Entity\Account The filtered Account
* @return Account The filtered Account.
*/
public static function api_account_external( $user_data, $user_id ) {
if ( $user_data || ( is_numeric( $user_id ) && $user_id ) ) {
@ -170,6 +259,78 @@ class Enable_Mastodon_Apps {
return $user_data;
}
/**
* Resolve internal accounts for Mastodon API
*
* @param Account $user_data The user data.
* @param string $user_id The user id.
*
* @return Account The filtered Account.
*/
public static function api_account_internal( $user_data, $user_id ) {
$user_id_to_use = self::maybe_map_user_to_blog( $user_id );
$user = Users::get_by_id( $user_id_to_use );
if ( ! $user || is_wp_error( $user ) ) {
return $user_data;
}
// Convert user to account.
$account = new Account();
// Even if we have a blog user, maintain the provided user_id so as not to confuse clients.
$account->id = (int) $user_id;
$account->username = $user->get_preferred_username();
$account->acct = $account->username;
$account->display_name = $user->get_name();
$account->note = $user->get_summary();
$account->source['note'] = wp_strip_all_tags( $account->note, true );
$account->url = $user->get_url();
$icon = $user->get_icon();
$account->avatar = $icon['url'];
$account->avatar_static = $account->avatar;
$header = $user->get_image();
if ( $header ) {
$account->header = $header['url'];
$account->header_static = $account->header;
}
$account->created_at = new DateTime( $user->get_published() );
$post_types = \get_option( 'activitypub_support_post_types', array( 'post' ) );
$query_args = array(
'post_type' => $post_types,
'posts_per_page' => 1,
);
if ( $user_id > 0 ) {
$query_args['author'] = $user_id;
}
$posts = \get_posts( $query_args );
$account->last_status_at = ! empty( $posts ) ? new DateTime( $posts[0]->post_date_gmt ) : $account->created_at;
$account->fields = self::get_extra_fields( $user_id_to_use );
// Now do it in source['fields'] with stripped tags.
$account->source['fields'] = \array_map(
function ( $field ) {
$field['value'] = \wp_strip_all_tags( $field['value'], true );
return $field;
},
$account->fields
);
$account->followers_count = Followers::count_followers( $user->get__id() );
return $account;
}
/**
* Get account for actor.
*
* @param string $uri The URI.
*
* @return Account|null The account.
*/
private static function get_account_for_actor( $uri ) {
if ( ! is_string( $uri ) ) {
return null;
@ -186,11 +347,11 @@ class Enable_Mastodon_Apps {
$acct = substr( $acct, 5 );
}
$account->id = $acct;
$account->username = $acct;
$account->acct = $acct;
$account->display_name = $data['name'];
$account->url = $uri;
$account->id = $acct;
$account->username = $acct;
$account->acct = $acct;
$account->display_name = $data['name'];
$account->url = $uri;
if ( ! empty( $data['summary'] ) ) {
$account->note = $data['summary'];
@ -217,6 +378,14 @@ class Enable_Mastodon_Apps {
return $account;
}
/**
* Search by URL for Mastodon API.
*
* @param array $search_data The search data.
* @param object $request The request object.
*
* @return array The filtered search data.
*/
public static function api_search_by_url( $search_data, $request ) {
$p = \wp_parse_url( $request->get_param( 'q' ) );
if ( ! $p || ! isset( $p['host'] ) ) {
@ -241,6 +410,14 @@ class Enable_Mastodon_Apps {
return $search_data;
}
/**
* Search for Mastodon API.
*
* @param array $search_data The search data.
* @param object $request The request object.
*
* @return array The filtered search data.
*/
public static function api_search( $search_data, $request ) {
$user_id = \get_current_user_id();
if ( ! $user_id ) {
@ -267,20 +444,20 @@ class Enable_Mastodon_Apps {
$acct = $follower->get_url();
}
$account = new Account();
$account->id = \strval( $follower->get__id() );
$account->username = $follower->get_preferred_username();
$account->acct = $acct;
$account->display_name = $follower->get_name();
$account->url = $follower->get_url();
$account->uri = $follower->get_id();
$account->avatar = $follower->get_icon_url();
$account->avatar_static = $follower->get_icon_url();
$account->created_at = new DateTime( $follower->get_published() );
$account = new Account();
$account->id = \strval( $follower->get__id() );
$account->username = $follower->get_preferred_username();
$account->acct = $acct;
$account->display_name = $follower->get_name();
$account->url = $follower->get_url();
$account->uri = $follower->get_id();
$account->avatar = $follower->get_icon_url();
$account->avatar_static = $follower->get_icon_url();
$account->created_at = new DateTime( $follower->get_published() );
$account->last_status_at = new DateTime( $follower->get_published() );
$account->note = $follower->get_summary();
$account->header = $follower->get_image_url();
$account->header_static = $follower->get_image_url();
$account->note = $follower->get_summary();
$account->header = $follower->get_image_url();
$account->header_static = $follower->get_image_url();
$search_data['accounts'][] = $account;
}
@ -288,6 +465,13 @@ class Enable_Mastodon_Apps {
return $search_data;
}
/**
* Get posts query args for Mastodon API.
*
* @param array $args The query arguments.
*
* @return array The filtered args.
*/
public static function api_get_posts_query_args( $args ) {
if ( isset( $args['author'] ) && is_string( $args['author'] ) ) {
$uri = Webfinger_Util::resolve( $args['author'] );
@ -300,6 +484,14 @@ class Enable_Mastodon_Apps {
return $args;
}
/**
* Convert an activity to a status.
*
* @param array $item The activity.
* @param Account $account The account.
*
* @return Status|null The status.
*/
private static function activity_to_status( $item, $account ) {
if ( isset( $item['object'] ) ) {
$object = $item['object'];
@ -311,7 +503,7 @@ class Enable_Mastodon_Apps {
return null;
}
$status = new Status();
$status = new Status();
$status->id = $object['id'];
$status->created_at = new DateTime( $object['published'] );
$status->content = $object['content'];
@ -335,20 +527,20 @@ class Enable_Mastodon_Apps {
$status->media_attachments = array_map(
function ( $attachment ) {
$default_attachment = array(
'url' => null,
'url' => null,
'mediaType' => null,
'name' => null,
'width' => 0,
'height' => 0,
'blurhash' => null,
'name' => null,
'width' => 0,
'height' => 0,
'blurhash' => null,
);
$attachment = array_merge( $default_attachment, $attachment );
$media_attachment = new Media_Attachment();
$media_attachment->id = $attachment['url'];
$media_attachment->type = strtok( $attachment['mediaType'], '/' );
$media_attachment->url = $attachment['url'];
$media_attachment = new Media_Attachment();
$media_attachment->id = $attachment['url'];
$media_attachment->type = strtok( $attachment['mediaType'], '/' );
$media_attachment->url = $attachment['url'];
$media_attachment->preview_url = $attachment['url'];
$media_attachment->description = $attachment['name'];
if ( $attachment['blurhash'] ) {
@ -372,6 +564,14 @@ class Enable_Mastodon_Apps {
return $status;
}
/**
* Get posts for Mastodon API.
*
* @param array $statuses The statuses.
* @param array $args The arguments.
*
* @return array The filtered statuses.
*/
public static function api_statuses_external( $statuses, $args ) {
if ( ! isset( $args['activitypub'] ) ) {
return $statuses;
@ -400,8 +600,8 @@ class Enable_Mastodon_Apps {
$limit = 40;
}
$activitypub_statuses = array();
$url = $outbox['first'];
$tries = 0;
$url = $outbox['first'];
$tries = 0;
while ( $url ) {
if ( ++$tries > 3 ) {
break;
@ -412,7 +612,7 @@ class Enable_Mastodon_Apps {
return $statuses;
}
$new_statuses = array_map(
$new_statuses = array_map(
function ( $item ) use ( $account, $args ) {
if ( $args['exclude_replies'] ) {
if ( isset( $item['object']['inReplyTo'] ) && $item['object']['inReplyTo'] ) {
@ -424,7 +624,7 @@ class Enable_Mastodon_Apps {
$posts['orderedItems']
);
$activitypub_statuses = array_merge( $activitypub_statuses, array_filter( $new_statuses ) );
$url = $posts['next'];
$url = $posts['next'];
if ( count( $activitypub_statuses ) >= $limit ) {
break;
@ -434,6 +634,15 @@ class Enable_Mastodon_Apps {
return array_slice( $activitypub_statuses, 0, $limit );
}
/**
* Get replies for Mastodon API.
*
* @param array $context The context.
* @param int $post_id The post id.
* @param string $url The URL.
*
* @return array The filtered context.
*/
public static function api_get_replies( $context, $post_id, $url ) {
$meta = Http::get_remote_object( $url, true );
if ( is_wp_error( $meta ) || ! isset( $meta['replies']['first']['next'] ) ) {
@ -441,7 +650,7 @@ class Enable_Mastodon_Apps {
}
$replies_url = $meta['replies']['first']['next'];
$replies = Http::get_remote_object( $replies_url, true );
$replies = Http::get_remote_object( $replies_url, true );
if ( is_wp_error( $replies ) || ! isset( $replies['items'] ) ) {
return $context;
}
@ -457,7 +666,7 @@ class Enable_Mastodon_Apps {
}
$account = self::get_account_for_actor( $status['attributedTo'] );
$status = self::activity_to_status( $status, $account );
$status = self::activity_to_status( $status, $account );
if ( $status ) {
$context['descendants'][ $status->id ] = $status;
}

View File

@ -1,21 +1,40 @@
<?php
/**
* Jetpack integration file.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
/**
* Jetpack integration class.
*/
class Jetpack {
/**
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_filter( 'jetpack_sync_post_meta_whitelist', [ __CLASS__, 'add_sync_meta' ] );
\add_filter( 'jetpack_sync_post_meta_whitelist', array( self::class, 'add_sync_meta' ) );
}
public static function add_sync_meta( $whitelist ) {
if ( ! is_array( $whitelist ) ) {
return $whitelist;
/**
* Add ActivityPub meta keys to the Jetpack sync allow list.
*
* @param array $allow_list The Jetpack sync allow list.
*
* @return array The Jetpack sync allow list with ActivityPub meta keys.
*/
public static function add_sync_meta( $allow_list ) {
if ( ! is_array( $allow_list ) ) {
return $allow_list;
}
$activitypub_meta_keys = [
$activitypub_meta_keys = array(
'activitypub_user_id',
'activitypub_inbox',
'activitypub_actor_json',
];
return \array_merge( $whitelist, $activitypub_meta_keys );
);
return \array_merge( $allow_list, $activitypub_meta_keys );
}
}

View File

@ -1,4 +1,10 @@
<?php
/**
* NodeInfo integration file.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
use function Activitypub\get_total_users;
@ -6,28 +12,28 @@ use function Activitypub\get_active_users;
use function Activitypub\get_rest_url_by_path;
/**
* Compatibility with the NodeInfo plugin
* Compatibility with the NodeInfo plugin.
*
* @see https://wordpress.org/plugins/nodeinfo/
*/
class Nodeinfo {
/**
* Initialize the class, registering WordPress hooks
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_filter( 'nodeinfo_data', array( self::class, 'add_nodeinfo_data' ), 10, 2 );
\add_filter( 'nodeinfo2_data', array( self::class, 'add_nodeinfo2_data' ), 10 );
\add_filter( 'nodeinfo2_data', array( self::class, 'add_nodeinfo2_data' ) );
\add_filter( 'wellknown_nodeinfo_data', array( self::class, 'add_wellknown_nodeinfo_data' ), 10, 2 );
}
/**
* Extend NodeInfo data
* Extend NodeInfo data.
*
* @param array $nodeinfo NodeInfo data
* @param string The NodeInfo Version
* @param array $nodeinfo NodeInfo data.
* @param string $version The NodeInfo Version.
*
* @return array The extended array
* @return array The extended array.
*/
public static function add_nodeinfo_data( $nodeinfo, $version ) {
if ( $version >= '2.0' ) {
@ -47,11 +53,11 @@ class Nodeinfo {
}
/**
* Extend NodeInfo2 data
* Extend NodeInfo2 data.
*
* @param array $nodeinfo NodeInfo2 data
* @param array $nodeinfo NodeInfo2 data.
*
* @return array The extended array
* @return array The extended array.
*/
public static function add_nodeinfo2_data( $nodeinfo ) {
$nodeinfo['protocols'][] = 'activitypub';
@ -66,15 +72,15 @@ class Nodeinfo {
}
/**
* Extend the well-known nodeinfo data
* Extend the well-known nodeinfo data.
*
* @param array $data The well-known nodeinfo data
* @param array $data The well-known nodeinfo data.
*
* @return array The extended array
* @return array The extended array.
*/
public static function add_wellknown_nodeinfo_data( $data ) {
$data['links'][] = array(
'rel' => 'https://www.w3.org/ns/activitystreams#Application',
'rel' => 'https://www.w3.org/ns/activitystreams#Application',
'href' => get_rest_url_by_path( 'application' ),
);

View File

@ -1,4 +1,10 @@
<?php
/**
* Opengraph integration file.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
use Activitypub\Model\Blog;
@ -8,7 +14,7 @@ use function Activitypub\is_single_user;
use function Activitypub\is_user_type_disabled;
/**
* Compatibility with the OpenGraph plugin
* Compatibility with the OpenGraph plugin.
*
* @see https://wordpress.org/plugins/opengraph/
* @see https://codeberg.org/fediverse/fep/src/branch/main/fep/XXXX/fep-XXXX.md
@ -16,7 +22,7 @@ use function Activitypub\is_user_type_disabled;
*/
class Opengraph {
/**
* Initialize the class, registering WordPress hooks
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
if ( ! function_exists( 'opengraph_metadata' ) ) {
@ -48,27 +54,27 @@ class Opengraph {
* @return array the updated metadata.
*/
public static function add_metadata( $metadata ) {
// Always show Blog-User if the Blog is in single user mode
// Always show Blog-User if the Blog is in single user mode.
if ( is_single_user() ) {
$user = new Blog();
// add WebFinger resource
// Add WebFinger resource.
$metadata['fediverse:creator'] = $user->get_webfinger();
return $metadata;
}
if ( \is_author() ) {
// Use the Author of the Archive-Page
// Use the Author of the Archive-Page.
$user_id = \get_queried_object_id();
} elseif ( \is_singular() ) {
// Use the Author of the Post
// Use the Author of the Post.
$user_id = \get_post_field( 'post_author', \get_queried_object_id() );
} elseif ( ! is_user_type_disabled( 'blog' ) ) {
// Use the Blog-User for any other page, if the Blog-User is not disabled
// Use the Blog-User for any other page, if the Blog-User is not disabled.
$user_id = Users::BLOG_USER_ID;
} else {
// Do not add any metadata otherwise
// Do not add any metadata otherwise.
return $metadata;
}
@ -78,7 +84,7 @@ class Opengraph {
return $metadata;
}
// add WebFinger resource
// Add WebFinger resource.
$metadata['fediverse:creator'] = $user->get_webfinger();
return $metadata;

View File

@ -0,0 +1,68 @@
<?php
/**
* Seriously Simple Podcasting integration file.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
use Activitypub\Transformer\Post;
use function Activitypub\generate_post_summary;
/**
* Compatibility with the Seriously Simple Podcasting plugin.
*
* This is a transformer for the Seriously Simple Podcasting plugin,
* that extends the default transformer for WordPress posts.
*
* @see https://wordpress.org/plugins/seriously-simple-podcasting/
*/
class Seriously_Simple_Podcasting extends Post {
/**
* Gets the attachment for a podcast episode.
*
* This method is overridden to add the audio file as an attachment.
*
* @return array The attachments array.
*/
public function get_attachment() {
$post = $this->wp_object;
$attachments = parent::get_attachment();
$attachment = array(
'type' => \esc_attr( \get_post_meta( $post->ID, 'episode_type', true ) ),
'url' => \esc_url( \get_post_meta( $post->ID, 'audio_file', true ) ),
'name' => \esc_attr( \get_the_title( $post->ID ) ),
'icon' => \esc_url( \get_post_meta( $post->ID, 'cover_image', true ) ),
);
$attachment = array_filter( $attachment );
array_unshift( $attachments, $attachment );
return $attachments;
}
/**
* Gets the object type for a podcast episode.
*
* Always returns 'Note' for the best possible compatibility with ActivityPub.
*
* @return string The object type.
*/
public function get_type() {
return 'Note';
}
/**
* Returns the content for the ActivityPub Item.
*
* The content will be generated based on the user settings.
*
* @return string The content.
*/
public function get_content() {
return generate_post_summary( $this->wp_object );
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* Stream Connector integration file.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
/**
* Stream Connector for ActivityPub.
*
* This class is a Stream Connector for the Stream plugin.
*
* @see https://wordpress.org/plugins/stream/
*/
class Stream_Connector extends \WP_Stream\Connector {
/**
* Connector slug.
*
* @var string
*/
public $name = 'activitypub';
/**
* Actions registered for this connector.
*
* @var array
*/
public $actions = array(
'activitypub_notification_follow',
);
/**
* Return translated connector label.
*
* @return string
*/
public function get_label() {
return __( 'ActivityPub', 'activitypub' );
}
/**
* Return translated context labels.
*
* @return array
*/
public function get_context_labels() {
return array();
}
/**
* Return translated action labels.
*
* @return array
*/
public function get_action_labels() {
return array();
}
/**
* Callback for activitypub_notification_follow.
*
* @param \Activitypub\Notification $notification The notification object.
*/
public function callback_activitypub_notification_follow( $notification ) {
$this->log(
sprintf(
// translators: %s is a URL.
__( 'New Follower: %s', 'activitypub' ),
$notification->actor
),
array(
'notification' => \wp_json_encode( $notification, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ),
),
null,
'notification',
$notification->type,
$notification->target
);
}
}

View File

@ -1,9 +1,16 @@
<?php
/**
* WebFinger integration file.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
use Activitypub\Rest\Webfinger as Webfinger_Rest;
use Activitypub\Collection\Users as User_Collection;
use function Activitypub\get_rest_url_by_path;
/**
* Compatibility with the WebFinger plugin
*
@ -11,7 +18,7 @@ use Activitypub\Collection\Users as User_Collection;
*/
class Webfinger {
/**
* Initialize the class, registering WordPress hooks
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_filter( 'webfinger_user_data', array( self::class, 'add_user_discovery' ), 1, 3 );
@ -19,51 +26,89 @@ class Webfinger {
}
/**
* Add WebFinger discovery links
* Add WebFinger discovery links.
*
* @param array $array the jrd array
* @param string $resource the WebFinger resource
* @param WP_User $user the WordPress user
* @param array $jrd The jrd array.
* @param string $uri The WebFinger resource.
* @param \WP_User $user The WordPress user.
*
* @return array the jrd array
* @return array The jrd array.
*/
public static function add_user_discovery( $array, $resource, $user ) {
public static function add_user_discovery( $jrd, $uri, $user ) {
$user = User_Collection::get_by_id( $user->ID );
if ( ! $user || is_wp_error( $user ) ) {
return $array;
return $jrd;
}
$array['subject'] = sprintf( 'acct:%s', $user->get_webfinger() );
$jrd['subject'] = sprintf( 'acct:%s', $user->get_webfinger() );
$array['aliases'][] = $user->get_url();
$array['aliases'][] = $user->get_alternate_url();
$jrd['aliases'][] = $user->get_url();
$jrd['aliases'][] = $user->get_alternate_url();
$array['links'][] = array(
$jrd['links'][] = array(
'rel' => 'self',
'type' => 'application/activity+json',
'href' => $user->get_url(),
);
return $array;
$jrd['links'][] = array(
'rel' => 'http://ostatus.org/schema/1.0/subscribe',
'template' => get_rest_url_by_path( 'interactions?uri={uri}' ),
);
return $jrd;
}
/**
* Add WebFinger discovery links
* Add WebFinger discovery links.
*
* @param array $array the jrd array
* @param string $resource the WebFinger resource
* @param WP_User $user the WordPress user
* @param array $jrd The jrd array.
* @param string $uri The WebFinger resource.
*
* @return array the jrd array
* @return array|\WP_Error The jrd array or WP_Error.
*/
public static function add_pseudo_user_discovery( $array, $resource ) {
$user = Webfinger_Rest::get_profile( $resource );
public static function add_pseudo_user_discovery( $jrd, $uri ) {
$user = User_Collection::get_by_resource( $uri );
if ( ! $user || is_wp_error( $user ) ) {
return $array;
if ( \is_wp_error( $user ) ) {
return $user;
}
return $user;
$aliases = array(
$user->get_url(),
$user->get_alternate_url(),
);
$aliases = array_unique( $aliases );
$profile = array(
'subject' => sprintf( 'acct:%s', $user->get_webfinger() ),
'aliases' => array_values( array_unique( $aliases ) ),
'links' => array(
array(
'rel' => 'self',
'type' => 'application/activity+json',
'href' => $user->get_url(),
),
array(
'rel' => 'http://webfinger.net/rel/profile-page',
'type' => 'text/html',
'href' => $user->get_url(),
),
array(
'rel' => 'http://ostatus.org/schema/1.0/subscribe',
'template' => get_rest_url_by_path( 'interactions?uri={uri}' ),
),
),
);
if ( 'Person' !== $user->get_type() ) {
$profile['links'][0]['properties'] = array(
'https://www.w3.org/ns/activitystreams#type' => $user->get_type(),
);
}
return $profile;
}
}

View File

@ -0,0 +1,155 @@
<?php
/**
* Load the ActivityPub integrations.
*
* @package Activitypub
*/
namespace Activitypub\Integration;
/**
* Initialize the ActivityPub integrations.
*/
function plugin_init() {
/**
* Adds WebFinger (plugin) support.
*
* This class handles the compatibility with the WebFinger plugin
* and coordinates the internal WebFinger implementation.
*
* @see https://wordpress.org/plugins/webfinger/
*/
require_once __DIR__ . '/class-webfinger.php';
Webfinger::init();
/**
* Adds NodeInfo (plugin) support.
*
* This class handles the compatibility with the NodeInfo plugin
* and coordinates the internal NodeInfo implementation.
*
* @see https://wordpress.org/plugins/nodeinfo/
*/
require_once __DIR__ . '/class-nodeinfo.php';
Nodeinfo::init();
/**
* Adds Enable Mastodon Apps support.
*
* This class handles the compatibility with the Enable Mastodon Apps plugin.
*
* @see https://wordpress.org/plugins/enable-mastodon-apps/
*/
if ( \defined( 'ENABLE_MASTODON_APPS_VERSION' ) ) {
require_once __DIR__ . '/class-enable-mastodon-apps.php';
Enable_Mastodon_Apps::init();
}
/**
* Adds OpenGraph support.
*
* This class handles the compatibility with the OpenGraph plugin.
*
* @see https://wordpress.org/plugins/opengraph/
*/
if ( '1' === \get_option( 'activitypub_use_opengraph', '1' ) ) {
require_once __DIR__ . '/class-opengraph.php';
Opengraph::init();
}
/**
* Adds Jetpack support.
*
* This class handles the compatibility with Jetpack.
*
* @see https://jetpack.com/
*/
if ( \defined( 'JETPACK__VERSION' ) && ! \defined( 'IS_WPCOM' ) ) {
require_once __DIR__ . '/class-jetpack.php';
Jetpack::init();
}
/**
* Adds Seriously Simple Podcasting support.
*
* This class handles the compatibility with Seriously Simple Podcasting.
*
* @see https://wordpress.org/plugins/seriously-simple-podcasting/
*/
if ( \defined( 'SSP_VERSION' ) ) {
add_filter(
'activitypub_transformer',
function ( $transformer, $data, $object_class ) {
if (
'WP_Post' === $object_class &&
\get_post_meta( $data->ID, 'audio_file', true )
) {
require_once __DIR__ . '/class-seriously-simple-podcasting.php';
return new Seriously_Simple_Podcasting( $data );
}
return $transformer;
},
10,
3
);
}
}
\add_action( 'plugins_loaded', __NAMESPACE__ . '\plugin_init' );
/**
* Register the Stream Connector for ActivityPub.
*
* @param array $classes The Stream connectors.
*
* @return array The Stream connectors with the ActivityPub connector.
*/
function register_stream_connector( $classes ) {
require plugin_dir_path( __FILE__ ) . '/class-stream-connector.php';
$class_name = '\Activitypub\Integration\Stream_Connector';
if ( ! class_exists( $class_name ) ) {
return;
}
wp_stream_get_instance();
$class = new $class_name();
if ( ! method_exists( $class, 'is_dependency_satisfied' ) ) {
return;
}
if ( $class->is_dependency_satisfied() ) {
$classes[] = $class;
}
return $classes;
}
add_filter( 'wp_stream_connectors', __NAMESPACE__ . '\register_stream_connector' );
// Excluded ActivityPub post types from the Stream.
add_filter(
'wp_stream_posts_exclude_post_types',
function ( $post_types ) {
$post_types[] = 'ap_follower';
$post_types[] = 'ap_extrafield';
$post_types[] = 'ap_extrafield_blog';
return $post_types;
}
);
/**
* Load the BuddyPress integration.
*
* Only load code that needs BuddyPress to run once BP is loaded and initialized.
*
* @see https://buddypress.org/
*/
add_action(
'bp_include',
function () {
require_once __DIR__ . '/class-buddypress.php';
Buddypress::init();
},
0
);