Merge branch 'master' into feature/165/fediverse-biography
This commit is contained in:
commit
9966427fd3
25 changed files with 382 additions and 199 deletions
52
README.md
52
README.md
|
@ -12,11 +12,11 @@ The ActivityPub protocol is a decentralized social networking protocol based upo
|
|||
|
||||
## Description ##
|
||||
|
||||
This is **BETA** software, see the FAQ to see the current feature set or rather what is still planned.
|
||||
This is BETA software, see the FAQ to see the current feature set or rather what is still planned.
|
||||
|
||||
The plugin implements the ActivityPub protocol for your blog. Your readers will be able to follow your blogposts on Mastodon and other federated platforms that support ActivityPub.
|
||||
The plugin implements the ActivityPub protocol for your blog, which means that your readers will be able to follow your blog posts on Mastodon and other federated platforms that support ActivityPub. In addition, replies to your posts on Mastodon and related platforms will automatically become comments on your blog post.
|
||||
|
||||
The plugin works with the following federated platforms:
|
||||
The plugin works with the following tested federated platforms, but there may be more that it works with as well:
|
||||
|
||||
* [Mastodon](https://joinmastodon.org/)
|
||||
* [Pleroma](https://pleroma.social/)
|
||||
|
@ -26,8 +26,44 @@ The plugin works with the following federated platforms:
|
|||
* [SocialHome](https://socialhome.network/)
|
||||
* [Misskey](https://join.misskey.page/)
|
||||
|
||||
Here’s what that means and what you can expect.
|
||||
|
||||
Once the ActivityPub plugin is installed, each author’s page on your WordPress blog will become its own federated instance. In other words, if you have two authors, Jane and Bob, on your website, `example.com`, then your authors would have their own author pages at `example.com/author/jane` and `example.com/author/bob`. Each of those author pages would now be available to Mastodon users (and all other federated platform users) as a profile that can be followed. Let’s break that down further. Let’s say you have a friend on Mastodon who tells you to follow them and they give you their profile name `@janelivesheresomeofthetime@mastodon.social`. You search for her name, see her profile, and click the follow button, right? From then on, everything Jane posts on her profile shows up in your Home feed. Okay, similarly, now that Jane has installed the ActivityPub plugin on her `example.com` site, her friends can also follow her on Mastodon by searching for `@jane@example.com` and clicking the Follow button on that profile.
|
||||
|
||||
From now on, every blog post Jane publishes on example.com will show up on your Home feed because you follow her `@jane@example.com` profile.
|
||||
Of course, if no one follows your author instance, then no one will ever see the posts - including you! So the easiest way to even know if the plugin is working is to follow your new profile yourself. If you already have a Mastodon profile, just follow your new one from there.
|
||||
|
||||
Some things to note:
|
||||
|
||||
1. Many single-author blogs have chosen to turn off or redirect their author profile pages, usually via an SEO plugin like Yoast or Rank Math. This is usually done to avoid duplicate content with your blog’s home page. If your author page has been deactivated in this way, then ActivityPub won’t work for you. Instead, you can turn your author profile page back on, and then use the option in your SEO plugin to noindex the author page. This will enable the page to be live and ActivityPub will now work, but the live page won’t cause any duplicate content issues with search engines.
|
||||
1. Once ActivityPub is installed, only new posts going forward will be available in the fediverse. Likewise, even if you’ve been using ActivityPub for a while, anyone who follows your site, will only see new posts you publish from that moment on. They will never see previously-published posts in their Home feed. This process is very similar to subscribing to a newsletter. If you subscribe to a newsletter, you will only receive future emails, but not the old archived ones. With ActivityPub, if someone follows your site, they will only receive new blog posts you publish from then on.
|
||||
|
||||
So what’s the process?
|
||||
|
||||
1. Install the ActivityPub plugin.
|
||||
1. Go to the plugin’s settings page and adjust the settings to your liking. Click the Save button when ready.
|
||||
1. Make sure your blog’s author profile page is active.
|
||||
1. Go to Mastodon or any other federated platform, search for your author’s new federated profile, and follow it. Your new profile will be in the form of @yourauthorname@yourwebsite.com, so that is what you’ll search for.
|
||||
1. On your blog, publish a new post.
|
||||
1. From Mastodon, check to see if the new post appears in your Home feed.
|
||||
|
||||
Please note that it may take up to 15 minutes or so for the new post to show up in your federated feed. This is because the messages are sent to the federated platforms using a delayed cron. This avoids breaking the publishing process for those cases where users might have lots of followers. So please don’t assume that just because you didn’t see it show up right away that something is broken. Give it some time. In most cases, it will show up within a few minutes, and you’ll know everything is working as expected.
|
||||
|
||||
## Frequently Asked Questions ##
|
||||
|
||||
### tl;dr ###
|
||||
|
||||
This plugin connects your WordPress blog to popular social platforms like Mastodon, making your posts more accessible to a wider audience. Once installed, your blog's author pages can be followed by users on these platforms, allowing them to receive your new posts in their feeds.
|
||||
|
||||
Here's how it works:
|
||||
|
||||
1. Install the plugin and adjust settings as needed.
|
||||
1. Ensure your blog's author profile page is active.
|
||||
1. On Mastodon or other supported platforms, search for and follow your author's new profile (e.g., `@yourauthorname@yourwebsite.com`).
|
||||
1. Publish a new post on your blog and check if it appears in your Mastodon feed.
|
||||
|
||||
Please note that it may take up to 15 minutes for a new post to appear in your feed, as messages are sent on a delay to avoid overwhelming your followers. Be patient and give it some time.
|
||||
|
||||
### What is the status of this plugin? ###
|
||||
|
||||
Implemented:
|
||||
|
@ -50,16 +86,6 @@ To implement:
|
|||
|
||||
*ActivityPub for WordPress* extends WordPress with some Fediverse features, but it does not compete with platforms like Friendica or Mastodon. If you want to run a **decentralized social network**, please use [Mastodon](https://joinmastodon.org/) or [GNU social](https://gnusocial.network/).
|
||||
|
||||
### What are the differences between this plugin and Pterotype? ###
|
||||
|
||||
**Compatibility**
|
||||
|
||||
*ActivityPub for WordPress* is compatible with OStatus and IndieWeb plugin suites. *Pterotype* is incompatible with the standalone [WebFinger plugin](https://wordpress.org/plugins/webfinger/), so it can't be run together with OStatus.
|
||||
|
||||
**Custom tables**
|
||||
|
||||
*Pterotype* creates/uses a bunch of custom tables, *ActivityPub for WordPress* only uses the native tables and adds as little meta data as possible.
|
||||
|
||||
### What if you are running your blog in a subdirectory? ###
|
||||
|
||||
In order for webfinger to work, it must be mapped to the root directory of the URL on which your blog resides.
|
||||
|
|
|
@ -40,50 +40,50 @@ function init() {
|
|||
require_once \dirname( __FILE__ ) . '/includes/model/class-post.php';
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-activity-dispatcher.php';
|
||||
\Activitypub\Activity_Dispatcher::init();
|
||||
Activity_Dispatcher::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-activitypub.php';
|
||||
\Activitypub\Activitypub::init();
|
||||
Activitypub::init();
|
||||
|
||||
// Configure the REST API route
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-outbox.php';
|
||||
\Activitypub\Rest\Outbox::init();
|
||||
Rest\Outbox::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-inbox.php';
|
||||
\Activitypub\Rest\Inbox::init();
|
||||
Rest\Inbox::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-followers.php';
|
||||
\Activitypub\Rest\Followers::init();
|
||||
Rest\Followers::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-following.php';
|
||||
\Activitypub\Rest\Following::init();
|
||||
Rest\Following::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-webfinger.php';
|
||||
\Activitypub\Rest\Webfinger::init();
|
||||
Rest\Webfinger::init();
|
||||
|
||||
// load NodeInfo endpoints only if blog is public
|
||||
if ( true === (bool) \get_option( 'blog_public', 1 ) ) {
|
||||
require_once \dirname( __FILE__ ) . '/includes/rest/class-nodeinfo.php';
|
||||
\Activitypub\Rest\NodeInfo::init();
|
||||
Rest\NodeInfo::init();
|
||||
}
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-admin.php';
|
||||
\Activitypub\Admin::init();
|
||||
Admin::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-hashtag.php';
|
||||
\Activitypub\Hashtag::init();
|
||||
Hashtag::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-shortcodes.php';
|
||||
\Activitypub\Shortcodes::init();
|
||||
Shortcodes::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-mention.php';
|
||||
\Activitypub\Mention::init();
|
||||
Mention::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-debug.php';
|
||||
\Activitypub\Debug::init();
|
||||
Debug::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-health-check.php';
|
||||
\Activitypub\Health_Check::init();
|
||||
Health_Check::init();
|
||||
|
||||
if ( \WP_DEBUG ) {
|
||||
require_once \dirname( __FILE__ ) . '/includes/debug.php';
|
||||
|
@ -137,6 +137,6 @@ function flush_rewrite_rules() {
|
|||
*/
|
||||
function enable_buddypress_features() {
|
||||
require_once \dirname( __FILE__ ) . '/integration/class-buddypress.php';
|
||||
\Activitypub\Integration\Buddypress::init();
|
||||
Integration\Buddypress::init();
|
||||
}
|
||||
add_action( 'bp_include', '\Activitypub\enable_buddypress_features' );
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Model\Post;
|
||||
use Activitypub\Model\Activity;
|
||||
|
||||
/**
|
||||
* ActivityPub Activity_Dispatcher Class
|
||||
*
|
||||
|
@ -13,85 +16,62 @@ class Activity_Dispatcher {
|
|||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'activitypub_send_post_activity', array( '\Activitypub\Activity_Dispatcher', 'send_post_activity' ) );
|
||||
\add_action( 'activitypub_send_update_activity', array( '\Activitypub\Activity_Dispatcher', 'send_update_activity' ) );
|
||||
\add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity' ) );
|
||||
// legacy
|
||||
\add_action( 'activitypub_send_post_activity', array( self::class, 'send_create_activity' ) );
|
||||
|
||||
\add_action( 'activitypub_send_create_activity', array( self::class, 'send_create_activity' ) );
|
||||
\add_action( 'activitypub_send_update_activity', array( self::class, 'send_update_activity' ) );
|
||||
\add_action( 'activitypub_send_delete_activity', array( self::class, 'send_delete_activity' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "create" activities.
|
||||
*
|
||||
* @param \Activitypub\Model\Post $activitypub_post
|
||||
* @param Activitypub\Model\Post $activitypub_post
|
||||
*/
|
||||
public static function send_post_activity( Model\Post $activitypub_post ) {
|
||||
// get latest version of post
|
||||
$user_id = $activitypub_post->get_post_author();
|
||||
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
|
||||
$inboxes = \Activitypub\get_follower_inboxes( $user_id );
|
||||
|
||||
$followers_url = \get_rest_url( null, '/activitypub/1.0/users/' . intval( $user_id ) . '/followers' );
|
||||
foreach ( $activitypub_activity->get_cc() as $cc ) {
|
||||
if ( $cc === $followers_url ) {
|
||||
continue;
|
||||
}
|
||||
$inbox = \Activitypub\get_inbox_by_actor( $cc );
|
||||
if ( ! $inbox || \is_wp_error( $inbox ) ) {
|
||||
continue;
|
||||
}
|
||||
// init array if empty
|
||||
if ( ! isset( $inboxes[ $inbox ] ) ) {
|
||||
$inboxes[ $inbox ] = array();
|
||||
}
|
||||
$inboxes[ $inbox ][] = $cc;
|
||||
}
|
||||
|
||||
foreach ( $inboxes as $inbox => $to ) {
|
||||
$to = array_values( array_unique( $to ) );
|
||||
$activitypub_activity->set_to( $to );
|
||||
$activity = $activitypub_activity->to_json();
|
||||
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
public static function send_create_activity( Post $activitypub_post ) {
|
||||
self::send_activity( $activitypub_post, 'Create' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "update" activities.
|
||||
*
|
||||
* @param \Activitypub\Model\Post $activitypub_post
|
||||
* @param Activitypub\Model\Post $activitypub_post
|
||||
*/
|
||||
public static function send_update_activity( $activitypub_post ) {
|
||||
// get latest version of post
|
||||
$user_id = $activitypub_post->get_post_author();
|
||||
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Update', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
|
||||
foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) {
|
||||
$activitypub_activity->set_to( $to );
|
||||
$activity = $activitypub_activity->to_json(); // phpcs:ignore
|
||||
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
public static function send_update_activity( Post $activitypub_post ) {
|
||||
self::send_activity( $activitypub_post, 'Update' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "delete" activities.
|
||||
*
|
||||
* @param \Activitypub\Model\Post $activitypub_post
|
||||
* @param Activitypub\Model\Post $activitypub_post
|
||||
*/
|
||||
public static function send_delete_activity( $activitypub_post ) {
|
||||
public static function send_delete_activity( Post $activitypub_post ) {
|
||||
self::send_activity( $activitypub_post, 'Delete' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param Activitypub\Model\Post $activitypub_post
|
||||
* @param [type] $activity_type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function send_activity( Post $activitypub_post, $activity_type ) {
|
||||
// get latest version of post
|
||||
$user_id = $activitypub_post->get_post_author();
|
||||
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Delete', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity = new Activity( $activity_type );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
|
||||
foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) {
|
||||
$activitypub_activity->set_to( $to );
|
||||
$activity = $activitypub_activity->to_json(); // phpcs:ignore
|
||||
$inboxes = \Activitypub\get_follower_inboxes( $user_id, $activitypub_activity->get_cc() );
|
||||
|
||||
foreach ( $inboxes as $inbox => $cc ) {
|
||||
$cc = array_values( array_unique( $cc ) );
|
||||
$activitypub_activity->add_cc( $cc );
|
||||
$activity = $activitypub_activity->to_json();
|
||||
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ class Activitypub {
|
|||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'template_include', array( '\Activitypub\Activitypub', 'render_json_template' ), 99 );
|
||||
\add_filter( 'query_vars', array( '\Activitypub\Activitypub', 'add_query_vars' ) );
|
||||
\add_filter( 'pre_get_avatar_data', array( '\Activitypub\Activitypub', 'pre_get_avatar_data' ), 11, 2 );
|
||||
\add_filter( 'template_include', array( self::class, 'render_json_template' ), 99 );
|
||||
\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 support for ActivityPub to custom post types
|
||||
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) : array();
|
||||
|
@ -22,9 +22,9 @@ class Activitypub {
|
|||
\add_post_type_support( $post_type, 'activitypub' );
|
||||
}
|
||||
|
||||
\add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 33, 3 );
|
||||
\add_action( 'wp_trash_post', array( '\Activitypub\Activitypub', 'trash_post' ), 1 );
|
||||
\add_action( 'untrash_post', array( '\Activitypub\Activitypub', 'untrash_post' ), 1 );
|
||||
\add_action( 'transition_post_status', array( self::class, 'schedule_post_activity' ), 33, 3 );
|
||||
\add_action( 'wp_trash_post', array( self::class, 'trash_post' ), 1 );
|
||||
\add_action( 'untrash_post', array( self::class, 'untrash_post' ), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,6 +39,9 @@ class Activitypub {
|
|||
return $template;
|
||||
}
|
||||
|
||||
// Ensure that edge caches know that this page can deliver both HTML and JSON.
|
||||
header( 'Vary: Accept' );
|
||||
|
||||
// check if user can publish posts
|
||||
if ( \is_author() && ! user_can( \get_the_author_meta( 'ID' ), 'publish_posts' ) ) {
|
||||
return $template;
|
||||
|
@ -117,7 +120,7 @@ class Activitypub {
|
|||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
|
||||
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
|
||||
\wp_schedule_single_event( \time(), 'activitypub_send_post_activity', array( $activitypub_post ) );
|
||||
\wp_schedule_single_event( \time(), 'activitypub_send_create_activity', array( $activitypub_post ) );
|
||||
} elseif ( 'publish' === $new_status ) {
|
||||
\wp_schedule_single_event( \time(), 'activitypub_send_update_activity', array( $activitypub_post ) );
|
||||
} elseif ( 'trash' === $new_status ) {
|
||||
|
|
|
@ -11,11 +11,11 @@ class Admin {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'admin_menu', array( '\Activitypub\Admin', 'admin_menu' ) );
|
||||
\add_action( 'admin_init', array( '\Activitypub\Admin', 'register_settings' ) );
|
||||
\add_action( 'show_user_profile', array( '\Activitypub\Admin', 'add_fediverse_profile' ) );
|
||||
\add_action( 'personal_options_update', array( '\Activitypub\Admin', 'save_user_description' ) );
|
||||
\add_action( 'admin_enqueue_scripts', array( '\Activitypub\Admin', 'enqueue_scripts' ) );
|
||||
\add_action( 'admin_menu', array( self::class, 'admin_menu' ) );
|
||||
\add_action( 'admin_init', array( self::class, 'register_settings' ) );
|
||||
\add_action( 'show_user_profile', array( self::class, 'add_fediverse_profile' ) );
|
||||
\add_action( 'personal_options_update', array( self::class, 'save_user_description' ) );
|
||||
\add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_scripts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,14 +27,14 @@ class Admin {
|
|||
'ActivityPub',
|
||||
'manage_options',
|
||||
'activitypub',
|
||||
array( '\Activitypub\Admin', 'settings_page' )
|
||||
array( self::class, 'settings_page' )
|
||||
);
|
||||
|
||||
\add_action( 'load-' . $settings_page, array( '\Activitypub\Admin', 'add_settings_help_tab' ) );
|
||||
\add_action( 'load-' . $settings_page, array( self::class, 'add_settings_help_tab' ) );
|
||||
|
||||
$followers_list_page = \add_users_page( \__( 'Followers', 'activitypub' ), \__( 'Followers (Fediverse)', 'activitypub' ), 'read', 'activitypub-followers-list', array( '\Activitypub\Admin', 'followers_list_page' ) );
|
||||
$followers_list_page = \add_users_page( \__( 'Followers', 'activitypub' ), \__( 'Followers', 'activitypub' ), 'read', 'activitypub-followers-list', array( self::class, 'followers_list_page' ) );
|
||||
|
||||
\add_action( 'load-' . $followers_list_page, array( '\Activitypub\Admin', 'add_followers_list_help_tab' ) );
|
||||
\add_action( 'load-' . $followers_list_page, array( self::class, 'add_followers_list_help_tab' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use WP_DEBUG;
|
||||
use WP_DEBUG_LOG;
|
||||
|
||||
/**
|
||||
* ActivityPub Debug Class
|
||||
*
|
||||
|
@ -12,7 +15,7 @@ class Debug {
|
|||
*/
|
||||
public static function init() {
|
||||
if ( WP_DEBUG && WP_DEBUG_LOG ) {
|
||||
\add_action( 'activitypub_safe_remote_post_response', array( '\Activitypub\Debug', 'log_remote_post_responses' ), 10, 4 );
|
||||
\add_action( 'activitypub_safe_remote_post_response', array( self::class, 'log_remote_post_responses' ), 10, 4 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ class Hashtag {
|
|||
*/
|
||||
public static function init() {
|
||||
if ( '1' === \get_option( 'activitypub_use_hashtags', '1' ) ) {
|
||||
\add_filter( 'wp_insert_post', array( '\Activitypub\Hashtag', 'insert_post' ), 10, 2 );
|
||||
\add_filter( 'the_content', array( '\Activitypub\Hashtag', 'the_content' ), 10, 2 );
|
||||
\add_filter( 'wp_insert_post', array( self::class, 'insert_post' ), 10, 2 );
|
||||
\add_filter( 'the_content', array( self::class, 'the_content' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,19 +14,19 @@ class Health_Check {
|
|||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'site_status_tests', array( '\Activitypub\Health_Check', 'add_tests' ) );
|
||||
\add_filter( 'debug_information', array( '\Activitypub\Health_Check', 'debug_information' ) );
|
||||
\add_filter( 'site_status_tests', array( self::class, 'add_tests' ) );
|
||||
\add_filter( 'debug_information', array( self::class, 'debug_information' ) );
|
||||
}
|
||||
|
||||
public static function add_tests( $tests ) {
|
||||
$tests['direct']['activitypub_test_author_url'] = array(
|
||||
'label' => \__( 'Author URL test', 'activitypub' ),
|
||||
'test' => array( '\Activitypub\Health_Check', 'test_author_url' ),
|
||||
'test' => array( self::class, 'test_author_url' ),
|
||||
);
|
||||
|
||||
$tests['direct']['activitypub_test_webfinger'] = array(
|
||||
'label' => __( 'WebFinger Test', 'activitypub' ),
|
||||
'test' => array( '\Activitypub\Health_Check', 'test_webfinger' ),
|
||||
'test' => array( self::class, 'test_webfinger' ),
|
||||
);
|
||||
|
||||
return $tests;
|
||||
|
@ -35,7 +35,7 @@ class Health_Check {
|
|||
/**
|
||||
* Author URL tests
|
||||
*
|
||||
* @return void
|
||||
* @return array
|
||||
*/
|
||||
public static function test_author_url() {
|
||||
$result = array(
|
||||
|
@ -73,7 +73,7 @@ class Health_Check {
|
|||
/**
|
||||
* WebFinger tests
|
||||
*
|
||||
* @return void
|
||||
* @return array
|
||||
*/
|
||||
public static function test_webfinger() {
|
||||
$result = array(
|
||||
|
@ -85,7 +85,7 @@ class Health_Check {
|
|||
),
|
||||
'description' => \sprintf(
|
||||
'<p>%s</p>',
|
||||
\__( 'Your WebFinger endpoint is accessible and returns the correct informations.', 'activitypub' )
|
||||
\__( 'Your WebFinger endpoint is accessible and returns the correct information.', 'activitypub' )
|
||||
),
|
||||
'actions' => '',
|
||||
'test' => 'test_webfinger',
|
||||
|
@ -109,9 +109,9 @@ class Health_Check {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if `author_posts_url` is accessible and that requerst returns correct JSON
|
||||
* Check if `author_posts_url` is accessible and that request returns correct JSON
|
||||
*
|
||||
* @return boolean|WP_Error
|
||||
* @return boolean|\WP_Error
|
||||
*/
|
||||
public static function is_author_url_accessible() {
|
||||
$user = \wp_get_current_user();
|
||||
|
@ -194,9 +194,9 @@ class Health_Check {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if WebFinger endoint is accessible and profile requerst returns correct JSON
|
||||
* Check if WebFinger endpoint is accessible and profile request returns correct JSON
|
||||
*
|
||||
* @return boolean|WP_Error
|
||||
* @return boolean|\WP_Error
|
||||
*/
|
||||
public static function is_webfinger_endpoint_accessible() {
|
||||
$user = \wp_get_current_user();
|
||||
|
@ -272,7 +272,7 @@ class Health_Check {
|
|||
* Static function for generating site debug data when required.
|
||||
*
|
||||
* @param array $info The debug information to be added to the core information page.
|
||||
* @return array The filtered informations
|
||||
* @return array The filtered information
|
||||
*/
|
||||
public static function debug_information( $info ) {
|
||||
$info['activitypub'] = array(
|
||||
|
|
|
@ -11,8 +11,8 @@ class Mention {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'the_content', array( '\Activitypub\Mention', 'the_content' ), 99, 2 );
|
||||
\add_filter( 'activitypub_extract_mentions', array( '\Activitypub\Mention', 'extract_mentions' ), 99, 2 );
|
||||
\add_filter( 'the_content', array( self::class, 'the_content' ), 99, 2 );
|
||||
\add_filter( 'activitypub_extract_mentions', array( self::class, 'extract_mentions' ), 99, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,7 +46,13 @@ class Mention {
|
|||
$the_content
|
||||
);
|
||||
|
||||
$the_content = \preg_replace_callback( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/', array( '\Activitypub\Mention', 'replace_with_links' ), $the_content );
|
||||
$the_content = preg_replace_callback(
|
||||
'#<img.*?[^>]+>#i',
|
||||
$protect,
|
||||
$the_content
|
||||
);
|
||||
|
||||
$the_content = \preg_replace_callback( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/', array( self::class, 'replace_with_links' ), $the_content );
|
||||
|
||||
$the_content = str_replace( array_reverse( array_keys( $protected_tags ) ), array_reverse( array_values( $protected_tags ) ), $the_content );
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* ActivityPub WebFinger Class
|
||||
*
|
||||
|
@ -23,6 +25,9 @@ class Webfinger {
|
|||
}
|
||||
|
||||
$user = \get_user_by( 'id', $user_id );
|
||||
if ( ! $user ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $user->user_login . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
|
||||
}
|
||||
|
@ -40,7 +45,7 @@ class Webfinger {
|
|||
|
||||
$url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[2] . '/.well-known/webfinger' );
|
||||
if ( ! \wp_http_validate_url( $url ) ) {
|
||||
$response = new \WP_Error( 'invalid_webfinger_url', null, $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;
|
||||
}
|
||||
|
@ -56,7 +61,7 @@ class Webfinger {
|
|||
);
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
$link = new \WP_Error( 'webfinger_url_not_accessible', null, $url );
|
||||
$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;
|
||||
}
|
||||
|
@ -65,7 +70,7 @@ class Webfinger {
|
|||
$body = \json_decode( $body, true );
|
||||
|
||||
if ( empty( $body['links'] ) ) {
|
||||
$link = new \WP_Error( 'webfinger_url_invalid_response', null, $url );
|
||||
$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;
|
||||
}
|
||||
|
@ -77,7 +82,7 @@ class Webfinger {
|
|||
}
|
||||
}
|
||||
|
||||
$link = new \WP_Error( 'webfinger_url_no_activity_pub', null, $body );
|
||||
$link = new WP_Error( 'webfinger_url_no_activity_pub', null, $body );
|
||||
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $link;
|
||||
}
|
||||
|
|
|
@ -219,8 +219,11 @@ function get_publickey_by_actor( $actor, $key_id ) {
|
|||
return new \WP_Error( 'activitypub_no_public_key', \__( 'No "Public-Key" found', 'activitypub' ), $metadata );
|
||||
}
|
||||
|
||||
function get_follower_inboxes( $user_id ) {
|
||||
function get_follower_inboxes( $user_id, $cc = array() ) {
|
||||
$followers = \Activitypub\Peer\Followers::get_followers( $user_id );
|
||||
$followers = array_merge( $followers, $cc );
|
||||
$followers = array_unique( $followers );
|
||||
|
||||
$inboxes = array();
|
||||
|
||||
foreach ( $followers as $follower ) {
|
||||
|
|
|
@ -9,24 +9,38 @@ namespace Activitypub\Model;
|
|||
* @see https://www.w3.org/TR/activitypub/
|
||||
*/
|
||||
class Activity {
|
||||
private $context = array( 'https://www.w3.org/ns/activitystreams' );
|
||||
private $context = array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
array(
|
||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||
'PropertyValue' => 'schema:PropertyValue',
|
||||
'schema' => 'http://schema.org#',
|
||||
'pt' => 'https://joinpeertube.org/ns#',
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'value' => 'schema:value',
|
||||
'Hashtag' => 'as:Hashtag',
|
||||
'featured' => array(
|
||||
'@id' => 'toot:featured',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'featuredTags' => array(
|
||||
'@id' => 'toot:featuredTags',
|
||||
'@type' => '@id',
|
||||
),
|
||||
),
|
||||
);
|
||||
private $published = '';
|
||||
private $id = '';
|
||||
private $type = 'Create';
|
||||
private $actor = '';
|
||||
private $to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
private $cc = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
private $cc = array();
|
||||
private $object = null;
|
||||
|
||||
const TYPE_SIMPLE = 'simple';
|
||||
const TYPE_FULL = 'full';
|
||||
const TYPE_NONE = 'none';
|
||||
|
||||
public function __construct( $type = 'Create', $context = self::TYPE_SIMPLE ) {
|
||||
if ( 'none' === $context ) {
|
||||
public function __construct( $type = 'Create', $context = true ) {
|
||||
if ( true !== $context ) {
|
||||
$this->context = null;
|
||||
} elseif ( 'full' === $context ) {
|
||||
$this->context = \Activitypub\get_context();
|
||||
}
|
||||
|
||||
$this->type = \ucfirst( $type );
|
||||
|
@ -43,6 +57,20 @@ class Activity {
|
|||
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
|
||||
if ( \strncasecmp( $method, 'add', 3 ) === 0 ) {
|
||||
if ( ! is_array( $this->$var ) ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
|
||||
if ( is_array( $params[0] ) ) {
|
||||
$this->$var = array_merge( $this->$var, $params[0] );
|
||||
} else {
|
||||
array_push( $this->$var, $params[0] );
|
||||
}
|
||||
|
||||
$this->$var = array_unique( $this->$var );
|
||||
}
|
||||
}
|
||||
|
||||
public function from_post( Post $post ) {
|
||||
|
@ -51,7 +79,8 @@ class Activity {
|
|||
if ( isset( $object['published'] ) ) {
|
||||
$this->published = $object['published'];
|
||||
}
|
||||
$this->cc = array( \get_rest_url( null, '/activitypub/1.0/users/' . intval( $post->get_post_author() ) . '/followers' ) );
|
||||
|
||||
$this->add_to( \get_rest_url( null, '/activitypub/1.0/users/' . intval( $post->get_post_author() ) . '/followers' ) );
|
||||
|
||||
if ( isset( $this->object['attributedTo'] ) ) {
|
||||
$this->actor = $this->object['attributedTo'];
|
||||
|
@ -59,7 +88,7 @@ class Activity {
|
|||
|
||||
foreach ( $post->get_tags() as $tag ) {
|
||||
if ( 'Mention' === $tag['type'] ) {
|
||||
$this->cc[] = $tag['href'];
|
||||
$this->add_cc( $tag['href'] );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,13 @@ class Post {
|
|||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The Object URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* The Object Summary.
|
||||
*
|
||||
|
@ -104,6 +111,24 @@ class Post {
|
|||
'cite' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* List of audience
|
||||
*
|
||||
* Also used for visibility
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $to = array( 'https://www.w3.org/ns/activitystreams#Public' );
|
||||
|
||||
/**
|
||||
* List of audience
|
||||
*
|
||||
* Also used for visibility
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cc = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -111,6 +136,7 @@ class Post {
|
|||
*/
|
||||
public function __construct( $post ) {
|
||||
$this->post = \get_post( $post );
|
||||
$this->add_to( \get_rest_url( null, '/activitypub/1.0/users/' . intval( $this->get_post_author() ) . '/followers' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,6 +160,20 @@ class Post {
|
|||
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
|
||||
if ( \strncasecmp( $method, 'add', 3 ) === 0 ) {
|
||||
if ( ! is_array( $this->$var ) ) {
|
||||
$this->$var = $params[0];
|
||||
}
|
||||
|
||||
if ( is_array( $params[0] ) ) {
|
||||
$this->$var = array_merge( $this->$var, $params[0] );
|
||||
} else {
|
||||
array_push( $this->$var, $params[0] );
|
||||
}
|
||||
|
||||
$this->$var = array_unique( $this->$var );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,6 +186,7 @@ class Post {
|
|||
|
||||
$array = array(
|
||||
'id' => $this->get_id(),
|
||||
'url' => $this->get_url(),
|
||||
'type' => $this->get_object_type(),
|
||||
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_date_gmt ) ),
|
||||
'attributedTo' => \get_author_posts_url( $post->post_author ),
|
||||
|
@ -155,8 +196,8 @@ class Post {
|
|||
'contentMap' => array(
|
||||
\strstr( \get_locale(), '_', true ) => $this->get_content(),
|
||||
),
|
||||
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||||
'cc' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||||
'to' => $this->get_to(),
|
||||
'cc' => $this->get_cc(),
|
||||
'attachment' => $this->get_attachments(),
|
||||
'tag' => $this->get_tags(),
|
||||
);
|
||||
|
@ -174,13 +215,13 @@ class Post {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of an Activity Object
|
||||
* Returns the URL of an Activity Object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
if ( $this->id ) {
|
||||
return $this->id;
|
||||
public function get_url() {
|
||||
if ( $this->url ) {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
$post = $this->post;
|
||||
|
@ -191,11 +232,26 @@ class Post {
|
|||
$permalink = \get_permalink( $post );
|
||||
}
|
||||
|
||||
$this->id = $permalink;
|
||||
$this->url = $permalink;
|
||||
|
||||
return $permalink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of an Activity Object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
if ( $this->id ) {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
$this->id = $this->get_url();
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Image Attachments
|
||||
*
|
||||
|
@ -250,7 +306,32 @@ class Post {
|
|||
// get URLs for each image
|
||||
foreach ( $image_ids as $id ) {
|
||||
$alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
|
||||
|
||||
/**
|
||||
* If you use the Jetpack plugin and its Image CDN, aka Photon,
|
||||
* the image strings returned will use the Photon URL.
|
||||
* We don't want that since Fediverse instances already do caching on their end.
|
||||
* Let the CDN only be used for visitors of the site.
|
||||
*
|
||||
* Old versions of Jetpack used the Jetpack_Photon class to do this.
|
||||
* New versions use the Image_CDN class.
|
||||
* Let's handle both.
|
||||
*/
|
||||
if ( \class_exists( '\Automattic\Jetpack\Image_CDN\Image_CDN' ) ) {
|
||||
\remove_filter( 'image_downsize', array( \Automattic\Jetpack\Image_CDN\Image_CDN::instance(), 'filter_image_downsize' ) );
|
||||
} elseif ( \class_exists( 'Jetpack_Photon' ) ) {
|
||||
\remove_filter( 'image_downsize', array( \Jetpack_Photon::instance(), 'filter_image_downsize' ) );
|
||||
}
|
||||
|
||||
$thumbnail = \wp_get_attachment_image_src( $id, 'full' );
|
||||
|
||||
// Re-enable Photon now that the image URL has been built.
|
||||
if ( \class_exists( '\Automattic\Jetpack\Image_CDN\Image_CDN' ) ) {
|
||||
\add_filter( 'image_downsize', array( \Automattic\Jetpack\Image_CDN\Image_CDN::instance(), 'filter_image_downsize' ), 10, 3 );
|
||||
} elseif ( \class_exists( 'Jetpack_Photon' ) ) {
|
||||
\add_filter( 'image_downsize', array( \Jetpack_Photon::instance(), 'filter_image_downsize' ), 10, 3 );
|
||||
}
|
||||
|
||||
$mimetype = \get_post_mime_type( $id );
|
||||
|
||||
if ( $thumbnail ) {
|
||||
|
|
|
@ -13,7 +13,7 @@ class Followers {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Followers', 'register_routes' ) );
|
||||
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,7 +26,7 @@ class Followers {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Followers', 'get' ),
|
||||
'callback' => array( self::class, 'get' ),
|
||||
'args' => self::request_parameters(),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
|
|
|
@ -13,7 +13,7 @@ class Following {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Following', 'register_routes' ) );
|
||||
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,7 +26,7 @@ class Following {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Following', 'get' ),
|
||||
'callback' => array( self::class, 'get' ),
|
||||
'args' => self::request_parameters(),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
use Activitypub\Model\Activity;
|
||||
|
||||
/**
|
||||
* ActivityPub Inbox REST-Class
|
||||
*
|
||||
|
@ -13,13 +15,13 @@ class Inbox {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Inbox', 'register_routes' ) );
|
||||
\add_filter( 'rest_pre_serve_request', array( '\Activitypub\Rest\Inbox', 'serve_request' ), 11, 4 );
|
||||
\add_action( 'activitypub_inbox_follow', array( '\Activitypub\Rest\Inbox', 'handle_follow' ), 10, 2 );
|
||||
\add_action( 'activitypub_inbox_undo', array( '\Activitypub\Rest\Inbox', 'handle_unfollow' ), 10, 2 );
|
||||
//\add_action( 'activitypub_inbox_like', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
|
||||
//\add_action( 'activitypub_inbox_announce', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
|
||||
\add_action( 'activitypub_inbox_create', array( '\Activitypub\Rest\Inbox', 'handle_create' ), 10, 2 );
|
||||
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
|
||||
\add_filter( 'rest_pre_serve_request', array( self::class, 'serve_request' ), 11, 4 );
|
||||
\add_action( 'activitypub_inbox_follow', array( self::class, 'handle_follow' ), 10, 2 );
|
||||
\add_action( 'activitypub_inbox_undo', array( self::class, 'handle_unfollow' ), 10, 2 );
|
||||
//\add_action( 'activitypub_inbox_like', array( self::class, 'handle_reaction' ), 10, 2 );
|
||||
//\add_action( 'activitypub_inbox_announce', array( self::class, 'handle_reaction' ), 10, 2 );
|
||||
\add_action( 'activitypub_inbox_create', array( self::class, 'handle_create' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,7 +34,7 @@ class Inbox {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox_post' ),
|
||||
'callback' => array( self::class, 'shared_inbox_post' ),
|
||||
'args' => self::shared_inbox_post_parameters(),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
|
@ -45,13 +47,13 @@ class Inbox {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_post' ),
|
||||
'callback' => array( self::class, 'user_inbox_post' ),
|
||||
'args' => self::user_inbox_post_parameters(),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_get' ),
|
||||
'callback' => array( self::class, 'user_inbox_get' ),
|
||||
'args' => self::user_inbox_get_parameters(),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
|
@ -356,7 +358,7 @@ class Inbox {
|
|||
$inbox = \Activitypub\get_inbox_by_actor( $object['actor'] );
|
||||
|
||||
// send "Accept" activity
|
||||
$activity = new \Activitypub\Model\Activity( 'Accept', \Activitypub\Model\Activity::TYPE_SIMPLE );
|
||||
$activity = new Activity( 'Accept' );
|
||||
$activity->set_object( $object );
|
||||
$activity->set_actor( \get_author_posts_url( $user_id ) );
|
||||
$activity->set_to( $object['actor'] );
|
||||
|
|
|
@ -13,9 +13,9 @@ class Nodeinfo {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Nodeinfo', 'register_routes' ) );
|
||||
\add_filter( 'nodeinfo_data', array( '\Activitypub\Rest\Nodeinfo', 'add_nodeinfo_discovery' ), 10, 2 );
|
||||
\add_filter( 'nodeinfo2_data', array( '\Activitypub\Rest\Nodeinfo', 'add_nodeinfo2_discovery' ), 10 );
|
||||
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
|
||||
\add_filter( 'nodeinfo_data', array( self::class, 'add_nodeinfo_discovery' ), 10, 2 );
|
||||
\add_filter( 'nodeinfo2_data', array( self::class, 'add_nodeinfo2_discovery' ), 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,7 @@ class Nodeinfo {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'discovery' ),
|
||||
'callback' => array( self::class, 'discovery' ),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
)
|
||||
|
@ -40,7 +40,7 @@ class Nodeinfo {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo' ),
|
||||
'callback' => array( self::class, 'nodeinfo' ),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
)
|
||||
|
@ -52,7 +52,7 @@ class Nodeinfo {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo2' ),
|
||||
'callback' => array( self::class, 'nodeinfo2' ),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
)
|
||||
|
@ -75,13 +75,24 @@ class Nodeinfo {
|
|||
'version' => \get_bloginfo( 'version' ),
|
||||
);
|
||||
|
||||
$users = \count_users();
|
||||
$users = \get_users(
|
||||
array(
|
||||
'capability__in' => array( 'publish_posts' ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_array( $users ) ) {
|
||||
$users = count( $users );
|
||||
} else {
|
||||
$users = 1;
|
||||
}
|
||||
|
||||
$posts = \wp_count_posts();
|
||||
$comments = \wp_count_comments();
|
||||
|
||||
$nodeinfo['usage'] = array(
|
||||
'users' => array(
|
||||
'total' => (int) $users['total_users'],
|
||||
'total' => $users,
|
||||
),
|
||||
'localPosts' => (int) $posts->publish,
|
||||
'localComments' => (int) $comments->approved,
|
||||
|
|
|
@ -13,7 +13,7 @@ class Outbox {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Outbox', 'register_routes' ) );
|
||||
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,7 +26,7 @@ class Outbox {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox_get' ),
|
||||
'callback' => array( self::class, 'user_outbox_get' ),
|
||||
'args' => self::request_parameters(),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
|
@ -102,7 +102,8 @@ class Outbox {
|
|||
|
||||
foreach ( $posts as $post ) {
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_NONE );
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', false );
|
||||
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
$json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
use WP_Error;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* ActivityPub WebFinger REST-Class
|
||||
*
|
||||
|
@ -13,8 +16,8 @@ class Webfinger {
|
|||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Webfinger', 'register_routes' ) );
|
||||
\add_action( 'webfinger_user_data', array( '\Activitypub\Rest\Webfinger', 'add_webfinger_discovery' ), 10, 3 );
|
||||
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
|
||||
\add_action( 'webfinger_user_data', array( self::class, 'add_webfinger_discovery' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +30,7 @@ class Webfinger {
|
|||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( '\Activitypub\Rest\Webfinger', 'webfinger' ),
|
||||
'callback' => array( self::class, 'webfinger' ),
|
||||
'args' => self::request_parameters(),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
|
@ -45,22 +48,23 @@ class Webfinger {
|
|||
$resource = $request->get_param( 'resource' );
|
||||
|
||||
if ( \strpos( $resource, '@' ) === false ) {
|
||||
return new \WP_Error( 'activitypub_unsupported_resource', \__( 'Resource is invalid', 'activitypub' ), array( 'status' => 400 ) );
|
||||
return new WP_Error( 'activitypub_unsupported_resource', \__( 'Resource is invalid', 'activitypub' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$resource = \str_replace( 'acct:', '', $resource );
|
||||
|
||||
$resource_identifier = \substr( $resource, 0, \strrpos( $resource, '@' ) );
|
||||
$resource_host = \substr( \strrchr( $resource, '@' ), 1 );
|
||||
$resource_host = \str_replace( 'www.', '', \substr( \strrchr( $resource, '@' ), 1 ) );
|
||||
$blog_host = \str_replace( 'www.', '', \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) );
|
||||
|
||||
if ( \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) !== $resource_host ) {
|
||||
return new \WP_Error( 'activitypub_wrong_host', \__( 'Resource host does not match blog host', 'activitypub' ), array( 'status' => 404 ) );
|
||||
if ( $blog_host !== $resource_host ) {
|
||||
return new WP_Error( 'activitypub_wrong_host', \__( 'Resource host does not match blog host', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$user = \get_user_by( 'login', \esc_sql( $resource_identifier ) );
|
||||
|
||||
if ( ! $user || ! user_can( $user, 'publish_posts' ) ) {
|
||||
return new \WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) );
|
||||
if ( ! $user || ! \user_can( $user, 'publish_posts' ) ) {
|
||||
return new WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$json = array(
|
||||
|
@ -82,7 +86,7 @@ class Webfinger {
|
|||
),
|
||||
);
|
||||
|
||||
return new \WP_REST_Response( $json, 200 );
|
||||
return new WP_REST_Response( $json, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
58
readme.txt
58
readme.txt
|
@ -2,7 +2,7 @@
|
|||
Contributors: pfefferle, mediaformat, akirk, automattic
|
||||
Tags: OStatus, fediverse, activitypub, activitystream
|
||||
Requires at least: 4.7
|
||||
Tested up to: 6.1
|
||||
Tested up to: 6.2
|
||||
Stable tag: 0.17.0
|
||||
Requires PHP: 5.6
|
||||
License: MIT
|
||||
|
@ -12,11 +12,11 @@ The ActivityPub protocol is a decentralized social networking protocol based upo
|
|||
|
||||
== Description ==
|
||||
|
||||
This is **BETA** software, see the FAQ to see the current feature set or rather what is still planned.
|
||||
This is BETA software, see the FAQ to see the current feature set or rather what is still planned.
|
||||
|
||||
The plugin implements the ActivityPub protocol for your blog. Your readers will be able to follow your blogposts on Mastodon and other federated platforms that support ActivityPub.
|
||||
The plugin implements the ActivityPub protocol for your blog, which means that your readers will be able to follow your blog posts on Mastodon and other federated platforms that support ActivityPub. In addition, replies to your posts on Mastodon and related platforms will automatically become comments on your blog post.
|
||||
|
||||
The plugin works with the following federated platforms:
|
||||
The plugin works with the following tested federated platforms, but there may be more that it works with as well:
|
||||
|
||||
* [Mastodon](https://joinmastodon.org/)
|
||||
* [Pleroma](https://pleroma.social/)
|
||||
|
@ -26,8 +26,44 @@ The plugin works with the following federated platforms:
|
|||
* [SocialHome](https://socialhome.network/)
|
||||
* [Misskey](https://join.misskey.page/)
|
||||
|
||||
Here’s what that means and what you can expect.
|
||||
|
||||
Once the ActivityPub plugin is installed, each author’s page on your WordPress blog will become its own federated instance. In other words, if you have two authors, Jane and Bob, on your website, `example.com`, then your authors would have their own author pages at `example.com/author/jane` and `example.com/author/bob`. Each of those author pages would now be available to Mastodon users (and all other federated platform users) as a profile that can be followed. Let’s break that down further. Let’s say you have a friend on Mastodon who tells you to follow them and they give you their profile name `@janelivesheresomeofthetime@mastodon.social`. You search for her name, see her profile, and click the follow button, right? From then on, everything Jane posts on her profile shows up in your Home feed. Okay, similarly, now that Jane has installed the ActivityPub plugin on her `example.com` site, her friends can also follow her on Mastodon by searching for `@jane@example.com` and clicking the Follow button on that profile.
|
||||
|
||||
From now on, every blog post Jane publishes on example.com will show up on your Home feed because you follow her `@jane@example.com` profile.
|
||||
Of course, if no one follows your author instance, then no one will ever see the posts - including you! So the easiest way to even know if the plugin is working is to follow your new profile yourself. If you already have a Mastodon profile, just follow your new one from there.
|
||||
|
||||
Some things to note:
|
||||
|
||||
1. Many single-author blogs have chosen to turn off or redirect their author profile pages, usually via an SEO plugin like Yoast or Rank Math. This is usually done to avoid duplicate content with your blog’s home page. If your author page has been deactivated in this way, then ActivityPub won’t work for you. Instead, you can turn your author profile page back on, and then use the option in your SEO plugin to noindex the author page. This will enable the page to be live and ActivityPub will now work, but the live page won’t cause any duplicate content issues with search engines.
|
||||
1. Once ActivityPub is installed, only new posts going forward will be available in the fediverse. Likewise, even if you’ve been using ActivityPub for a while, anyone who follows your site, will only see new posts you publish from that moment on. They will never see previously-published posts in their Home feed. This process is very similar to subscribing to a newsletter. If you subscribe to a newsletter, you will only receive future emails, but not the old archived ones. With ActivityPub, if someone follows your site, they will only receive new blog posts you publish from then on.
|
||||
|
||||
So what’s the process?
|
||||
|
||||
1. Install the ActivityPub plugin.
|
||||
1. Go to the plugin’s settings page and adjust the settings to your liking. Click the Save button when ready.
|
||||
1. Make sure your blog’s author profile page is active.
|
||||
1. Go to Mastodon or any other federated platform, search for your author’s new federated profile, and follow it. Your new profile will be in the form of @yourauthorname@yourwebsite.com, so that is what you’ll search for.
|
||||
1. On your blog, publish a new post.
|
||||
1. From Mastodon, check to see if the new post appears in your Home feed.
|
||||
|
||||
Please note that it may take up to 15 minutes or so for the new post to show up in your federated feed. This is because the messages are sent to the federated platforms using a delayed cron. This avoids breaking the publishing process for those cases where users might have lots of followers. So please don’t assume that just because you didn’t see it show up right away that something is broken. Give it some time. In most cases, it will show up within a few minutes, and you’ll know everything is working as expected.
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= tl;dr =
|
||||
|
||||
This plugin connects your WordPress blog to popular social platforms like Mastodon, making your posts more accessible to a wider audience. Once installed, your blog's author pages can be followed by users on these platforms, allowing them to receive your new posts in their feeds.
|
||||
|
||||
Here's how it works:
|
||||
|
||||
1. Install the plugin and adjust settings as needed.
|
||||
1. Ensure your blog's author profile page is active.
|
||||
1. On Mastodon or other supported platforms, search for and follow your author's new profile (e.g., `@yourauthorname@yourwebsite.com`).
|
||||
1. Publish a new post on your blog and check if it appears in your Mastodon feed.
|
||||
|
||||
Please note that it may take up to 15 minutes for a new post to appear in your feed, as messages are sent on a delay to avoid overwhelming your followers. Be patient and give it some time.
|
||||
|
||||
= What is the status of this plugin? =
|
||||
|
||||
Implemented:
|
||||
|
@ -50,16 +86,6 @@ To implement:
|
|||
|
||||
*ActivityPub for WordPress* extends WordPress with some Fediverse features, but it does not compete with platforms like Friendica or Mastodon. If you want to run a **decentralized social network**, please use [Mastodon](https://joinmastodon.org/) or [GNU social](https://gnusocial.network/).
|
||||
|
||||
= What are the differences between this plugin and Pterotype? =
|
||||
|
||||
**Compatibility**
|
||||
|
||||
*ActivityPub for WordPress* is compatible with OStatus and IndieWeb plugin suites. *Pterotype* is incompatible with the standalone [WebFinger plugin](https://wordpress.org/plugins/webfinger/), so it can't be run together with OStatus.
|
||||
|
||||
**Custom tables**
|
||||
|
||||
*Pterotype* creates/uses a bunch of custom tables, *ActivityPub for WordPress* only uses the native tables and adds as little meta data as possible.
|
||||
|
||||
= What if you are running your blog in a subdirectory? =
|
||||
|
||||
In order for webfinger to work, it must be mapped to the root directory of the URL on which your blog resides.
|
||||
|
@ -87,6 +113,10 @@ Where 'blog' is the path to the subdirectory at which your blog resides.
|
|||
|
||||
Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
|
||||
|
||||
= Next =
|
||||
|
||||
* Compatibility: indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.2.
|
||||
|
||||
= 0.17.0 =
|
||||
|
||||
* Fix type-selector
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="wrap">
|
||||
<h1><?php \esc_html_e( 'Followers (Fediverse)', 'activitypub' ); ?></h1>
|
||||
<h1><?php \esc_html_e( 'Followers', 'activitypub' ); ?></h1>
|
||||
|
||||
<?php // translators: ?>
|
||||
<p><?php \printf( \esc_html__( 'You currently have %s followers.', 'activitypub' ), \esc_attr( \Activitypub\Peer\Followers::count_followers( \get_current_user_id() ) ) ); ?></p>
|
||||
|
|
|
@ -65,8 +65,6 @@
|
|||
<li><code>[ap_permalink]</code> - <?php \esc_html_e( 'The post\'s permalink.', 'activitypub' ); ?></li>
|
||||
<li><code>[ap_shortlink]</code> - <?php echo \wp_kses( \__( 'The post\'s shortlink. I can recommend <a href="https://wordpress.org/plugins/hum/" target="_blank">Hum</a>.', 'activitypub' ), 'default' ); ?></li>
|
||||
<li><code>[ap_hashtags]</code> - <?php \esc_html_e( 'The post\'s tags as hashtags.', 'activitypub' ); ?></li>
|
||||
<li><code>[ap_hashcats]</code> - <?php \esc_html_e( 'The post\'s categories as hashtags.', 'activitypub' ); ?></li>
|
||||
<li><code>[ap_image]</code> - <?php \esc_html_e( 'The URL for the post\'s featured image.', 'activitypub' ); ?></li>
|
||||
</ul>
|
||||
<p><?php \esc_html_e( 'You can find the full list with all possible attributes in the help section on the top-right of the screen.', 'activitypub' ); ?></p>
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,7 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT
|
|||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
\Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post );
|
||||
\Activitypub\Activity_Dispatcher::send_create_activity( $activitypub_post );
|
||||
|
||||
$this->assertNotEmpty( $activitypub_post->get_content() );
|
||||
|
||||
|
@ -69,7 +69,7 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT
|
|||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
\Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post );
|
||||
\Activitypub\Activity_Dispatcher::send_create_activity( $activitypub_post );
|
||||
|
||||
$this->assertNotEmpty( $activitypub_post->get_content() );
|
||||
|
||||
|
|
|
@ -19,10 +19,10 @@ class Test_Activitypub_Activity extends WP_UnitTestCase {
|
|||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create' );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
|
||||
$this->assertContains( \get_rest_url( null, '/activitypub/1.0/users/1/followers' ), $activitypub_activity->get_cc() );
|
||||
$this->assertContains( \get_rest_url( null, '/activitypub/1.0/users/1/followers' ), $activitypub_activity->get_to() );
|
||||
$this->assertContains( 'https://example.com/alex', $activitypub_activity->get_cc() );
|
||||
|
||||
remove_all_filters( 'activitypub_extract_mentions' );
|
||||
|
|
|
@ -31,6 +31,7 @@ ENDPRE;
|
|||
array( 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@<span>pfefferle</span>@notiz.blog</a> test', 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@<span>pfefferle</span>@notiz.blog</a> test' ),
|
||||
array( 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@pfefferle@notiz.blog</a> test', 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@pfefferle@notiz.blog</a> test' ),
|
||||
array( 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/@pfefferle/">@pfefferle@notiz.blog</a> test', 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/@pfefferle/">@pfefferle@notiz.blog</a> test' ),
|
||||
array( 'hallo <img src="abc" alt="https://notiz.blog/@pfefferle/" title="@pfefferle@notiz.blog"/> test', 'hallo <img src="abc" alt="https://notiz.blog/@pfefferle/" title="@pfefferle@notiz.blog"/> test' ),
|
||||
array( $code, $code ),
|
||||
array( $pre, $pre ),
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue