diff --git a/README.md b/README.md index 969f562..d1b4461 100644 --- a/README.md +++ b/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. diff --git a/activitypub.php b/activitypub.php index ca5b686..38886ff 100644 --- a/activitypub.php +++ b/activitypub.php @@ -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' ); diff --git a/includes/class-activity-dispatcher.php b/includes/class-activity-dispatcher.php index f8abd38..57fee1a 100644 --- a/includes/class-activity-dispatcher.php +++ b/includes/class-activity-dispatcher.php @@ -1,6 +1,9 @@ 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 ); } diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index c04aedc..16a1f22 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -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 ) { diff --git a/includes/class-admin.php b/includes/class-admin.php index 8287259..f1f7df8 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -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' ) ); } /** diff --git a/includes/class-debug.php b/includes/class-debug.php index 767d4a9..36f8bda 100644 --- a/includes/class-debug.php +++ b/includes/class-debug.php @@ -1,6 +1,9 @@ \__( '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( '

%s

', - \__( '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( diff --git a/includes/class-mention.php b/includes/class-mention.php index 7c8672a..9c93d80 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -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( + '#]+>#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 ); diff --git a/includes/class-webfinger.php b/includes/class-webfinger.php index ab33411..1581853 100644 --- a/includes/class-webfinger.php +++ b/includes/class-webfinger.php @@ -1,6 +1,8 @@ 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; } diff --git a/includes/functions.php b/includes/functions.php index 9b00e8f..a436496 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -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 ) { diff --git a/includes/model/class-activity.php b/includes/model/class-activity.php index f865d6d..34f6146 100644 --- a/includes/model/class-activity.php +++ b/includes/model/class-activity.php @@ -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'] ); } } diff --git a/includes/model/class-post.php b/includes/model/class-post.php index d83384e..a69e139 100644 --- a/includes/model/class-post.php +++ b/includes/model/class-post.php @@ -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 ) { diff --git a/includes/rest/class-followers.php b/includes/rest/class-followers.php index 2734c78..3b1e146 100644 --- a/includes/rest/class-followers.php +++ b/includes/rest/class-followers.php @@ -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', ), diff --git a/includes/rest/class-following.php b/includes/rest/class-following.php index d7caff4..52a95e7 100644 --- a/includes/rest/class-following.php +++ b/includes/rest/class-following.php @@ -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', ), diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 5d6b6ce..1a63108 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -1,6 +1,8 @@ \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'] ); diff --git a/includes/rest/class-nodeinfo.php b/includes/rest/class-nodeinfo.php index 3106c5e..980c24b 100644 --- a/includes/rest/class-nodeinfo.php +++ b/includes/rest/class-nodeinfo.php @@ -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, diff --git a/includes/rest/class-outbox.php b/includes/rest/class-outbox.php index 615f06b..905dfd5 100644 --- a/includes/rest/class-outbox.php +++ b/includes/rest/class-outbox.php @@ -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 } diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php index 0c6d5f1..10dcfa4 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger.php @@ -1,6 +1,9 @@ \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 ); } /** diff --git a/readme.txt b/readme.txt index 63e1f6a..e89c768 100644 --- a/readme.txt +++ b/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 diff --git a/templates/followers-list.php b/templates/followers-list.php index 057f498..a7136ce 100644 --- a/templates/followers-list.php +++ b/templates/followers-list.php @@ -1,5 +1,5 @@
-

+

diff --git a/templates/settings.php b/templates/settings.php index 97e7e4d..2bfaed4 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -65,8 +65,6 @@
  • [ap_permalink] -
  • [ap_shortlink] - Hum.', 'activitypub' ), 'default' ); ?>
  • [ap_hashtags] -
  • -
  • [ap_hashcats] -
  • -
  • [ap_image] -
  • diff --git a/tests/test-class-activitypub-activity-dispatcher.php b/tests/test-class-activitypub-activity-dispatcher.php index dea7e38..0e503e5 100644 --- a/tests/test-class-activitypub-activity-dispatcher.php +++ b/tests/test-class-activitypub-activity-dispatcher.php @@ -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() ); diff --git a/tests/test-class-activitypub-activity.php b/tests/test-class-activitypub-activity.php index 254d860..7fe6551 100644 --- a/tests/test-class-activitypub-activity.php +++ b/tests/test-class-activitypub-activity.php @@ -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' ); diff --git a/tests/test-class-activitypub-mention.php b/tests/test-class-activitypub-mention.php index 6f6b9ff..ca7395f 100644 --- a/tests/test-class-activitypub-mention.php +++ b/tests/test-class-activitypub-mention.php @@ -31,6 +31,7 @@ ENDPRE; array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ), array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ), array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ), + array( 'hallo https://notiz.blog/@pfefferle/ test', 'hallo https://notiz.blog/@pfefferle/ test' ), array( $code, $code ), array( $pre, $pre ), );