diff --git a/wp-content/plugins/activitypub/.distignore b/wp-content/plugins/activitypub/.distignore
new file mode 100644
index 00000000..93fbfdb6
--- /dev/null
+++ b/wp-content/plugins/activitypub/.distignore
@@ -0,0 +1,36 @@
+.DS_Store
+.editorconfig
+.git
+.gitignore
+.github
+.travis.yml
+.codeclimate.yml
+.data
+.svnignore
+.wordpress-org
+.php_cs
+Gruntfile.js
+LINGUAS
+Makefile
+README.md
+readme.md
+CODE_OF_CONDUCT.md
+LICENSE.md
+_site
+_config.yml
+bin
+composer.json
+composer.lock
+docker-compose.yml
+gulpfile.js
+package.json
+node_modules
+npm-debug.log
+phpcs.xml
+package.json
+package-lock.json
+phpunit.xml
+phpunit.xml.dist
+tests
+node_modules
+vendor
diff --git a/wp-content/plugins/activitypub/activitypub.php b/wp-content/plugins/activitypub/activitypub.php
index 3eef785e..c98b50c6 100644
--- a/wp-content/plugins/activitypub/activitypub.php
+++ b/wp-content/plugins/activitypub/activitypub.php
@@ -3,7 +3,7 @@
* Plugin Name: ActivityPub
* Plugin URI: https://github.com/pfefferle/wordpress-activitypub/
* Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
- * Version: 0.12.0
+ * Version: 0.13.0
* Author: Matthias Pfefferle
* Author URI: https://notiz.blog/
* License: MIT
@@ -54,7 +54,7 @@ function init() {
\Activitypub\Rest\Webfinger::init();
// load NodeInfo endpoints only if blog is public
- if ( 1 === \get_option( 'blog_public', 1 ) ) {
+ if ( true === (bool) \get_option( 'blog_public', 1 ) ) {
require_once \dirname( __FILE__ ) . '/includes/rest/class-nodeinfo.php';
\Activitypub\Rest\NodeInfo::init();
}
@@ -75,6 +75,10 @@ function init() {
\add_filter( 'wp_rest_server_class', function() {
return '\Activitypub\Rest\Server';
} );
+
+ if ( \WP_DEBUG ) {
+ require_once \dirname( __FILE__ ) . '/includes/debug.php';
+ }
}
\add_action( 'plugins_loaded', '\Activitypub\init' );
diff --git a/wp-content/plugins/activitypub/includes/class-activitypub.php b/wp-content/plugins/activitypub/includes/class-activitypub.php
index 6c7c0c89..c2a068da 100644
--- a/wp-content/plugins/activitypub/includes/class-activitypub.php
+++ b/wp-content/plugins/activitypub/includes/class-activitypub.php
@@ -34,14 +34,16 @@ class Activitypub {
* @return string The new path to the JSON template.
*/
public static function render_json_template( $template ) {
- if ( ! \is_author() && ! \is_singular() ) {
+ if ( ! \is_author() && ! \is_singular() && ! \is_home() ) {
return $template;
}
if ( \is_author() ) {
- $json_template = \dirname( __FILE__ ) . '/../templates/json-author.php';
+ $json_template = \dirname( __FILE__ ) . '/../templates/author-json.php';
} elseif ( \is_singular() ) {
- $json_template = \dirname( __FILE__ ) . '/../templates/json-post.php';
+ $json_template = \dirname( __FILE__ ) . '/../templates/post-json.php';
+ } elseif ( \is_home() ) {
+ $json_template = \dirname( __FILE__ ) . '/../templates/blog-json.php';
}
global $wp_query;
diff --git a/wp-content/plugins/activitypub/includes/class-health-check.php b/wp-content/plugins/activitypub/includes/class-health-check.php
index c9c0b849..12f8d710 100644
--- a/wp-content/plugins/activitypub/includes/class-health-check.php
+++ b/wp-content/plugins/activitypub/includes/class-health-check.php
@@ -7,27 +7,38 @@ namespace Activitypub;
* @author Matthias Pfefferle
*/
class Health_Check {
+
+ /**
+ * Initialize health checks
+ *
+ * @return void
+ */
public static function init() {
\add_filter( 'site_status_tests', array( '\Activitypub\Health_Check', 'add_tests' ) );
}
public static function add_tests( $tests ) {
- $tests['direct']['activitypub_test_profile_url'] = array(
- 'label' => \__( 'Profile URL test', 'activitypub' ),
- 'test' => array( '\Activitypub\Health_Check', 'test_profile_url' ),
+ $tests['direct']['activitypub_test_author_url'] = array(
+ 'label' => \__( 'Author URL test', 'activitypub' ),
+ 'test' => array( '\Activitypub\Health_Check', 'test_author_url' ),
);
- //$tests['direct']['activitypub_test_profile_url2'] = array(
- // 'label' => __( 'Profile URL Test', 'activitypub' ),
- // 'test' => array( '\Activitypub\Health_Check', 'test_profile_url' ),
- //);
+ $tests['direct']['activitypub_test_webfinger'] = array(
+ 'label' => __( 'WebFinger Test', 'activitypub' ),
+ 'test' => array( '\Activitypub\Health_Check', 'test_webfinger' ),
+ );
return $tests;
}
- public static function test_profile_url() {
+ /**
+ * Author URL tests
+ *
+ * @return void
+ */
+ public static function test_author_url() {
$result = array(
- 'label' => \__( 'Profile URL accessible', 'activitypub' ),
+ 'label' => \__( 'Author URL accessible', 'activitypub' ),
'status' => 'good',
'badge' => array(
'label' => \__( 'ActivityPub', 'activitypub' ),
@@ -35,49 +46,245 @@ class Health_Check {
),
'description' => \sprintf(
'
%s
',
- \__( 'Your profile URL is accessible and do not redirect to the home page.', 'activitypub' )
+ \__( 'Your author URL is accessible and supports the required "Accept" header.', 'activitypub' )
),
'actions' => '',
- 'test' => 'test_profile_url',
+ 'test' => 'test_author_url',
);
- $enum = self::is_profile_url_accessible();
+ $check = self::is_author_url_accessible();
- if ( true !== $enum ) {
- $result['status'] = 'critical';
- $result['label'] = \__( 'Profile URL is not accessible', 'activitypub' );
- $result['description'] = \sprintf(
- '%s
',
- \__( 'Authorization Headers are being blocked by your hosting provider. This will cause IndieAuth to fail.', 'activitypub' )
- );
+ if ( true === $check ) {
+ return $result;
}
+ $result['status'] = 'critical';
+ $result['label'] = \__( 'Author URL is not accessible', 'activitypub' );
+ $result['badge']['color'] = 'red';
+ $result['description'] = \sprintf(
+ '%s
',
+ $check->get_error_message()
+ );
+
return $result;
}
- public static function is_profile_url_accessible() {
+ /**
+ * WebFinger tests
+ *
+ * @return void
+ */
+ public static function test_webfinger() {
+ $result = array(
+ 'label' => \__( 'WebFinger endpoint', 'activitypub' ),
+ 'status' => 'good',
+ 'badge' => array(
+ 'label' => \__( 'ActivityPub', 'activitypub' ),
+ 'color' => 'green',
+ ),
+ 'description' => \sprintf(
+ '%s
',
+ \__( 'Your WebFinger endpoint is accessible and returns the correct informations.', 'activitypub' )
+ ),
+ 'actions' => '',
+ 'test' => 'test_webfinger',
+ );
+
+ $check = self::is_webfinger_endpoint_accessible();
+
+ if ( true === $check ) {
+ return $result;
+ }
+
+ $result['status'] = 'critical';
+ $result['label'] = \__( 'WebFinger endpoint is not accessible', 'activitypub' );
+ $result['badge']['color'] = 'red';
+ $result['description'] = \sprintf(
+ '%s
',
+ $check->get_error_message()
+ );
+
+ return $result;
+ }
+
+ /**
+ * Check if `author_posts_url` is accessible and that requerst returns correct JSON
+ *
+ * @return boolean|WP_Error
+ */
+ public static function is_author_url_accessible() {
$user = \wp_get_current_user();
$author_url = \get_author_posts_url( $user->ID );
+ $reference_author_url = self::get_author_posts_url( $user->ID, $user->user_nicename );
// check for "author" in URL
- if ( false === \strpos( $author_url, 'author' ) ) {
- return false;
+ if ( $author_url !== $reference_author_url ) {
+ return new \WP_Error(
+ 'author_url_not_accessible',
+ \sprintf(
+ // translators: %s: Author URL
+ \__(
+ 'Your author URL %s
was replaced, this is often done by plugins.
',
+ 'activitypub'
+ ),
+ $author_url
+ )
+ );
}
// try to access author URL
- $response = \wp_remote_get( $author_url, array( 'headers' => array( 'Accept' => 'application/activity+json' ) ) );
+ $response = \wp_remote_get(
+ $author_url,
+ array(
+ 'headers' => array( 'Accept' => 'application/activity+json' ),
+ 'redirection' => 0,
+ )
+ );
if ( \is_wp_error( $response ) ) {
- return false;
+ return new \WP_Error(
+ 'author_url_not_accessible',
+ \sprintf(
+ // translators: %s: Author URL
+ \__(
+ 'Your author URL %s
is not accessible. Please check your WordPress setup or permalink structure. If the setup seems fine, maybe check if a plugin might restrict the access.
',
+ 'activitypub'
+ ),
+ $author_url
+ )
+ );
+ }
+
+ $response_code = \wp_remote_retrieve_response_code( $response );
+
+ // check for redirects
+ if ( \in_array( $response_code, array( 301, 302, 307, 308 ), true ) ) {
+ return new \WP_Error(
+ 'author_url_not_accessible',
+ \sprintf(
+ // translators: %s: Author URL
+ \__(
+ 'Your author URL %s
is redirecting to another page, this is often done by SEO plugins like "Yoast SEO".
',
+ 'activitypub'
+ ),
+ $author_url
+ )
+ );
}
// check if response is JSON
$body = \wp_remote_retrieve_body( $response );
if ( ! \is_string( $body ) || ! \is_array( \json_decode( $body, true ) ) ) {
- return false;
+ return new \WP_Error(
+ 'author_url_not_accessible',
+ \sprintf(
+ // translators: %s: Author URL
+ \__(
+ 'Your author URL %s
does not return valid JSON for application/activity+json
. Please check if your hosting supports alternate Accept
headers.
',
+ 'activitypub'
+ ),
+ $author_url
+ )
+ );
}
return true;
}
+
+ /**
+ * Check if WebFinger endoint is accessible and profile requerst returns correct JSON
+ *
+ * @return boolean|WP_Error
+ */
+ public static function is_webfinger_endpoint_accessible() {
+ $user = \wp_get_current_user();
+ $webfinger = \Activitypub\get_webfinger_resource( $user->ID );
+
+ $url = \wp_parse_url( \home_url(), \PHP_URL_SCHEME ) . '://' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
+
+ if ( \wp_parse_url( \home_url(), \PHP_URL_PORT ) ) {
+ $url .= ':' . \wp_parse_url( \home_url(), \PHP_URL_PORT );
+ }
+
+ $url = \trailingslashit( $url ) . '.well-known/webfinger';
+
+ $url = \add_query_arg( 'resource', 'acct:' . $webfinger, $url );
+
+ // try to access author URL
+ $response = \wp_remote_get(
+ $url,
+ array(
+ 'headers' => array( 'Accept' => 'application/activity+json' ),
+ 'redirection' => 0,
+ )
+ );
+
+ if ( \is_wp_error( $response ) ) {
+ return new \WP_Error(
+ 'webfinger_url_not_accessible',
+ \sprintf(
+ // translators: %s: Author URL
+ \__(
+ 'Your WebFinger endpoint %s
is not accessible. Please check your WordPress setup or permalink structure.
',
+ 'activitypub'
+ ),
+ $url
+ )
+ );
+ }
+
+ $response_code = \wp_remote_retrieve_response_code( $response );
+
+ // check if response is JSON
+ $body = \wp_remote_retrieve_body( $response );
+
+ if ( ! \is_string( $body ) || ! \is_array( \json_decode( $body, true ) ) ) {
+ return new \WP_Error(
+ 'webfinger_url_not_accessible',
+ \sprintf(
+ // translators: %s: Author URL
+ \__(
+ 'Your WebFinger endpoint %s
does not return valid JSON for application/jrd+json
.
',
+ 'activitypub'
+ ),
+ $url
+ )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieve the URL to the author page for the user with the ID provided.
+ *
+ * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
+ *
+ * @param int $author_id Author ID.
+ * @param string $author_nicename Optional. The author's nicename (slug). Default empty.
+ *
+ * @return string The URL to the author's page.
+ */
+ public static function get_author_posts_url( $author_id, $author_nicename = '' ) {
+ global $wp_rewrite;
+ $auth_id = (int) $author_id;
+ $link = $wp_rewrite->get_author_permastruct();
+
+ if ( empty( $link ) ) {
+ $file = home_url( '/' );
+ $link = $file . '?author=' . $auth_id;
+ } else {
+ if ( '' === $author_nicename ) {
+ $user = get_userdata( $author_id );
+ if ( ! empty( $user->user_nicename ) ) {
+ $author_nicename = $user->user_nicename;
+ }
+ }
+ $link = str_replace( '%author%', $author_nicename, $link );
+ $link = home_url( user_trailingslashit( $link ) );
+ }
+
+ return $link;
+ }
}
diff --git a/wp-content/plugins/activitypub/includes/debug.php b/wp-content/plugins/activitypub/includes/debug.php
new file mode 100644
index 00000000..a5683f4e
--- /dev/null
+++ b/wp-content/plugins/activitypub/includes/debug.php
@@ -0,0 +1,16 @@
+ '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',
+ ),
),
);
diff --git a/wp-content/plugins/activitypub/includes/rest/class-followers.php b/wp-content/plugins/activitypub/includes/rest/class-followers.php
index 05a77890..f246edd0 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-followers.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-followers.php
@@ -21,7 +21,7 @@ class Followers {
*/
public static function register_routes() {
\register_rest_route(
- 'activitypub/1.0', '/users/(?P\d+)/followers', array(
+ 'activitypub/1.0', '/users/(?P\d+)/followers', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Followers', 'get' ),
@@ -40,7 +40,7 @@ class Followers {
* @return WP_REST_Response
*/
public static function get( $request ) {
- $user_id = $request->get_param( 'id' );
+ $user_id = $request->get_param( 'user_id' );
$user = \get_user_by( 'ID', $user_id );
if ( ! $user ) {
@@ -61,6 +61,11 @@ class Followers {
$json->{'@context'} = \Activitypub\get_context();
+ $json->id = \home_url( \add_query_arg( null, null ) );
+ $json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
+ $json->actor = \get_author_posts_url( $user_id );
+ $json->type = 'OrderedCollectionPage';
+
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/followers" ); // phpcs:ignore
$json->totalItems = \Activitypub\count_followers( $user_id ); // phpcs:ignore
$json->orderedItems = \Activitypub\Peer\Followers::get_followers( $user_id ); // phpcs:ignore
@@ -87,7 +92,7 @@ class Followers {
'type' => 'integer',
);
- $params['id'] = array(
+ $params['user_id'] = array(
'required' => true,
'type' => 'integer',
);
diff --git a/wp-content/plugins/activitypub/includes/rest/class-following.php b/wp-content/plugins/activitypub/includes/rest/class-following.php
index af48106d..8e375974 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-following.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-following.php
@@ -21,7 +21,7 @@ class Following {
*/
public static function register_routes() {
\register_rest_route(
- 'activitypub/1.0', '/users/(?P\d+)/following', array(
+ 'activitypub/1.0', '/users/(?P\d+)/following', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Following', 'get' ),
@@ -40,7 +40,7 @@ class Following {
* @return WP_REST_Response
*/
public static function get( $request ) {
- $user_id = $request->get_param( 'id' );
+ $user_id = $request->get_param( 'user_id' );
$user = \get_user_by( 'ID', $user_id );
if ( ! $user ) {
@@ -61,14 +61,17 @@ class Following {
$json->{'@context'} = \Activitypub\get_context();
+ $json->id = \home_url( \add_query_arg( null, null ) );
+ $json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
+ $json->actor = \get_author_posts_url( $user_id );
+ $json->type = 'OrderedCollectionPage';
+
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/following" ); // phpcs:ignore
$json->totalItems = 0; // phpcs:ignore
- $json->orderedItems = array(); // phpcs:ignore
+ $json->orderedItems = apply_filters( 'activitypub_following', array(), $user ); // phpcs:ignore
$json->first = $json->partOf; // phpcs:ignore
- $json->first = \get_rest_url( null, "/activitypub/1.0/users/$user_id/following" );
-
$response = new \WP_REST_Response( $json, 200 );
$response->header( 'Content-Type', 'application/activity+json' );
@@ -87,7 +90,7 @@ class Following {
'type' => 'integer',
);
- $params['id'] = array(
+ $params['user_id'] = array(
'required' => true,
'type' => 'integer',
);
diff --git a/wp-content/plugins/activitypub/includes/rest/class-inbox.php b/wp-content/plugins/activitypub/includes/rest/class-inbox.php
index 3b35f487..2b99b620 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-inbox.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-inbox.php
@@ -30,10 +30,15 @@ class Inbox {
'activitypub/1.0', '/inbox', array(
array(
'methods' => \WP_REST_Server::EDITABLE,
- 'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox' ),
+ 'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox_post' ),
'args' => self::shared_inbox_request_parameters(),
'permission_callback' => '__return_true',
),
+ array(
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => array( '\Activitypub\Rest\Inbox', 'inbox_get' ),
+ 'permission_callback' => '__return_true',
+ ),
)
);
@@ -41,10 +46,15 @@ class Inbox {
'activitypub/1.0', '/users/(?P\d+)/inbox', array(
array(
'methods' => \WP_REST_Server::EDITABLE,
- 'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox' ),
+ 'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_post' ),
'args' => self::user_inbox_request_parameters(),
'permission_callback' => '__return_true',
),
+ array(
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => array( '\Activitypub\Rest\Inbox', 'inbox_get' ),
+ 'permission_callback' => '__return_true',
+ ),
)
);
}
@@ -82,10 +92,53 @@ class Inbox {
* Renders the user-inbox
*
* @param WP_REST_Request $request
+ * @return WP_REST_Response
+ */
+ public static function inbox_get( $request ) {
+ $page = $request->get_param( 'page', 0 );
+
+ /*
+ * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ */
+ \do_action( 'activitypub_inbox_pre' );
+
+ $json = new \stdClass();
+
+ $json->{'@context'} = \Activitypub\get_context();
+ $json->id = \home_url( \add_query_arg( null, null ) );
+ $json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
+ $json->type = 'OrderedCollectionPage';
+ $json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/inbox" ); // phpcs:ignore
+
+ $json->totalItems = 0; // phpcs:ignore
+
+ $json->orderedItems = array(); // phpcs:ignore
+
+ $json->first = $json->partOf; // phpcs:ignore
+
+ // filter output
+ $json = \apply_filters( 'activitypub_inbox_array', $json );
+
+ /*
+ * Action triggerd after the ActivityPub profile has been created and sent to the client
+ */
+ \do_action( 'activitypub_inbox_post' );
+
+ $response = new \WP_REST_Response( $json, 200 );
+
+ $response->header( 'Content-Type', 'application/activity+json' );
+
+ return $response;
+ }
+
+ /**
+ * Handles user-inbox requests
+ *
+ * @param WP_REST_Request $request
*
* @return WP_REST_Response
*/
- public static function user_inbox( $request ) {
+ public static function user_inbox_post( $request ) {
$user_id = $request->get_param( 'user_id' );
$data = $request->get_params();
@@ -104,13 +157,28 @@ class Inbox {
*
* @return WP_REST_Response
*/
- public static function shared_inbox( $request ) {
+ public static function shared_inbox_post( $request ) {
$data = $request->get_params();
- $type = \strtoloer( $request->get_param( 'type' ) );
+ $type = $request->get_param( 'type' );
+
+ $users = self::extract_recipients( $data );
+
+ if ( ! $users ) {
+ return new \WP_Error( 'rest_invalid_param', \__( 'No recipients found', 'activitypub' ), array(
+ 'status' => 404,
+ 'params' => array(
+ 'to' => \__( 'Please check/validate "to" field', 'activitypub' ),
+ 'bto' => \__( 'Please check/validate "bto" field', 'activitypub' ),
+ 'cc' => \__( 'Please check/validate "cc" field', 'activitypub' ),
+ 'bcc' => \__( 'Please check/validate "bcc" field', 'activitypub' ),
+ 'audience' => \__( 'Please check/validate "audience" field', 'activitypub' ),
+ ),
+ ) );
+ }
foreach ( $users as $user ) {
- \do_action( 'activitypub_inbox', $data, $user_id, $type );
- \do_action( "activitypub_inbox_{$type}", $data, $user_id );
+ \do_action( 'activitypub_inbox', $data, $user->ID, $type );
+ \do_action( "activitypub_inbox_{$type}", $data, $user->ID );
}
return new \WP_REST_Response( array(), 202 );
@@ -208,7 +276,7 @@ class Inbox {
);
$params['to'] = array(
- 'required' => true,
+ 'required' => false,
'sanitize_callback' => function( $param, $request, $key ) {
if ( \is_string( $param ) ) {
$param = array( $param );
@@ -309,12 +377,18 @@ class Inbox {
),
);
+ // disable flood control
+ \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
+
// do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' );
$state = \wp_new_comment( $commentdata, true );
\remove_filter( 'pre_option_require_name_email', '__return_false' );
+
+ // re-add flood control
+ \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
}
/**
@@ -348,11 +422,46 @@ class Inbox {
),
);
+ // disable flood control
+ \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
+
// do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' );
$state = \wp_new_comment( $commentdata, true );
\remove_filter( 'pre_option_require_name_email', '__return_false' );
+
+ // re-add flood control
+ \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
+ }
+
+ public static function extract_recipients( $data ) {
+ $recipients = array();
+ $users = array();
+
+ foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
+ if ( array_key_exists( $i, $data ) ) {
+ $recipients = array_merge( $recipients, $data[ $i ] );
+ }
+
+ if ( array_key_exists( $i, $data['object'] ) ) {
+ $recipients = array_merge( $recipients, $data[ $i ] );
+ }
+ }
+
+ $recipients = array_unique( $recipients );
+
+ foreach ( $recipients as $recipient ) {
+ $user_id = \Activitypub\url_to_authorid( $recipient );
+
+ $user = get_user_by( 'id', $user_id );
+
+ if ( $user ) {
+ $users[] = $user;
+ }
+ }
+
+ return $users;
}
}
diff --git a/wp-content/plugins/activitypub/includes/rest/class-outbox.php b/wp-content/plugins/activitypub/includes/rest/class-outbox.php
index 5aa18f75..fb1e7813 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-outbox.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-outbox.php
@@ -21,10 +21,10 @@ class Outbox {
*/
public static function register_routes() {
\register_rest_route(
- 'activitypub/1.0', '/users/(?P\d+)/outbox', array(
+ 'activitypub/1.0', '/users/(?P\d+)/outbox', array(
array(
'methods' => \WP_REST_Server::READABLE,
- 'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox' ),
+ 'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox_get' ),
'args' => self::request_parameters(),
'permission_callback' => '__return_true',
),
@@ -38,8 +38,8 @@ class Outbox {
* @param WP_REST_Request $request
* @return WP_REST_Response
*/
- public static function user_outbox( $request ) {
- $user_id = $request->get_param( 'id' );
+ public static function user_outbox_get( $request ) {
+ $user_id = $request->get_param( 'user_id' );
$author = \get_user_by( 'ID', $user_id );
if ( ! $author ) {
@@ -70,24 +70,27 @@ class Outbox {
$count_posts = \wp_count_posts();
$json->totalItems = \intval( $count_posts->publish ); // phpcs:ignore
- $posts = \get_posts( array(
- 'posts_per_page' => 10,
- 'author' => $user_id,
- 'offset' => $page * 10,
- ) );
+ $json->first = \add_query_arg( 'page', 1, $json->partOf ); // phpcs:ignore
+ $json->last = \add_query_arg( 'page', \ceil ( $json->totalItems / 10 ), $json->partOf ); // phpcs:ignore
- $json->first = \add_query_arg( 'page', 0, $json->partOf ); // phpcs:ignore
- $json->last = \add_query_arg( 'page', ( \ceil ( $json->totalItems / 10 ) ) - 1, $json->partOf ); // phpcs:ignore
-
- if ( ( \ceil ( $json->totalItems / 10 ) ) - 1 > $page ) { // phpcs:ignore
- $json->next = \add_query_arg( 'page', ++$page, $json->partOf ); // phpcs:ignore
+ if ( $page && ( ( \ceil ( $json->totalItems / 10 ) ) > $page ) ) { // phpcs:ignore
+ $json->next = \add_query_arg( 'page', $page + 1, $json->partOf ); // phpcs:ignore
}
- 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->from_post( $activitypub_post->to_array() );
- $json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
+ if ( $page ) {
+ $posts = \get_posts( array(
+ 'posts_per_page' => 10,
+ 'author' => $user_id,
+ 'offset' => ( $page - 1 ) * 10,
+ 'post_type' => 'post',
+ ) );
+
+ 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->from_post( $activitypub_post->to_array() );
+ $json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore
+ }
}
// filter output
@@ -117,7 +120,7 @@ class Outbox {
'type' => 'integer',
);
- $params['id'] = array(
+ $params['user_id'] = array(
'required' => true,
'type' => 'integer',
);
diff --git a/wp-content/plugins/activitypub/languages/activitypub.pot b/wp-content/plugins/activitypub/languages/activitypub.pot
deleted file mode 100644
index 5af3b0ca..00000000
--- a/wp-content/plugins/activitypub/languages/activitypub.pot
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (C) 2020 Matthias Pfefferle
-# This file is distributed under the MIT.
-msgid ""
-msgstr ""
-"Project-Id-Version: ActivityPub 0.12.0\n"
-"Report-Msgid-Bugs-To: "
-"https://wordpress.org/support/plugin/wordpress-activitypub\n"
-"POT-Creation-Date: 2020-12-21 19:48:46+00:00\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2020-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"X-Generator: grunt-wp-i18n 1.0.3\n"
-
-#. Plugin Name of the plugin/theme
-msgid "ActivityPub"
-msgstr ""
-
-#. Plugin URI of the plugin/theme
-msgid "https://github.com/pfefferle/wordpress-activitypub/"
-msgstr ""
-
-#. Description of the plugin/theme
-msgid ""
-"The ActivityPub protocol is a decentralized social networking protocol "
-"based upon the ActivityStreams 2.0 data format."
-msgstr ""
-
-#. Author of the plugin/theme
-msgid "Matthias Pfefferle"
-msgstr ""
-
-#. Author URI of the plugin/theme
-msgid "https://notiz.blog/"
-msgstr ""
\ No newline at end of file
diff --git a/wp-content/plugins/activitypub/readme.txt b/wp-content/plugins/activitypub/readme.txt
index 2515ef0f..c816df4a 100644
--- a/wp-content/plugins/activitypub/readme.txt
+++ b/wp-content/plugins/activitypub/readme.txt
@@ -3,8 +3,8 @@ Contributors: pfefferle, mediaformat
Donate link: https://notiz.blog/donate/
Tags: OStatus, fediverse, activitypub, activitystream
Requires at least: 4.7
-Tested up to: 5.6
-Stable tag: 0.12.0
+Tested up to: 5.8
+Stable tag: 0.13.0
Requires PHP: 5.6
License: MIT
License URI: http://opensource.org/licenses/MIT
@@ -49,7 +49,7 @@ To implement:
= What is "ActivityPub for WordPress" =
-*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://gnu.io/social/).
+*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? =
@@ -88,6 +88,11 @@ 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).
+= 0.13.0 =
+
+* add Autor URL and WebFinger health checks
+* fix NodeInfo endpoint
+
= 0.12.0 =
* use "pre_option_require_name_email" filter instead of "check_comment_flood". props [@akirk](https://github.com/akirk)
diff --git a/wp-content/plugins/activitypub/templates/json-author.php b/wp-content/plugins/activitypub/templates/author-json.php
similarity index 94%
rename from wp-content/plugins/activitypub/templates/json-author.php
rename to wp-content/plugins/activitypub/templates/author-json.php
index a16de2fa..ccaa167f 100644
--- a/wp-content/plugins/activitypub/templates/json-author.php
+++ b/wp-content/plugins/activitypub/templates/author-json.php
@@ -82,12 +82,12 @@ $json->endpoints = array(
*/
// filter output
-$json = \apply_filters( 'activitypub_json_author_array', $json );
+$json = \apply_filters( 'activitypub_json_author_array', $json, $author_id );
/*
* Action triggerd prior to the ActivityPub profile being created and sent to the client
*/
-\do_action( 'activitypub_json_author_pre' );
+\do_action( 'activitypub_json_author_pre', $author_id );
$options = 0;
// JSON_PRETTY_PRINT added in PHP 5.4
@@ -102,7 +102,7 @@ $options |= \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT;
*
* @param int $options The current options flags
*/
-$options = \apply_filters( 'activitypub_json_author_options', $options );
+$options = \apply_filters( 'activitypub_json_author_options', $options, $author_id );
\header( 'Content-Type: application/activity+json' );
echo \wp_json_encode( $json, $options );
@@ -110,4 +110,4 @@ echo \wp_json_encode( $json, $options );
/*
* Action triggerd after the ActivityPub profile has been created and sent to the client
*/
-\do_action( 'activitypub_json_author_post' );
+\do_action( 'activitypub_json_author_post', $author_id );
diff --git a/wp-content/plugins/activitypub/templates/blog-json.php b/wp-content/plugins/activitypub/templates/blog-json.php
new file mode 100644
index 00000000..6377ab81
--- /dev/null
+++ b/wp-content/plugins/activitypub/templates/blog-json.php
@@ -0,0 +1,92 @@
+{'@context'} = \Activitypub\get_context();
+$json->id = \get_home_url( '/' );
+$json->type = 'Organization';
+$json->name = \get_bloginfo( 'name' );
+$json->summary = \html_entity_decode(
+ \get_bloginfo( 'description' ),
+ \ENT_QUOTES,
+ 'UTF-8'
+);
+$json->preferredUsername = \get_bloginfo( 'name' ); // phpcs:ignore
+$json->url = \get_home_url( '/' );
+
+if ( \has_site_icon() ) {
+ $json->icon = array(
+ 'type' => 'Image',
+ 'url' => \get_site_icon_url( 120 ),
+ );
+}
+
+if ( \has_header_image() ) {
+ $json->image = array(
+ 'type' => 'Image',
+ 'url' => \get_header_image(),
+ );
+}
+
+$json->inbox = \get_rest_url( null, "/activitypub/1.0/blog/inbox" );
+$json->outbox = \get_rest_url( null, "/activitypub/1.0/blog/outbox" );
+$json->followers = \get_rest_url( null, "/activitypub/1.0/blog/followers" );
+$json->following = \get_rest_url( null, "/activitypub/1.0/blog/following" );
+
+$json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_approves_followers', \__return_false() ); // phpcs:ignore
+
+// phpcs:ignore
+$json->publicKey = array(
+ 'id' => \get_home_url( '/' ) . '#main-key',
+ 'owner' => \get_home_url( '/' ),
+ 'publicKeyPem' => \trim(),
+);
+
+$json->tag = array();
+$json->attachment = array();
+
+$json->attachment[] = array(
+ 'type' => 'PropertyValue',
+ 'name' => \__( 'Blog', 'activitypub' ),
+ 'value' => \html_entity_decode(
+ '' . \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) . '',
+ \ENT_QUOTES,
+ 'UTF-8'
+ ),
+);
+
+/*
+$json->endpoints = array(
+ 'sharedInbox' => \get_rest_url( null, '/activitypub/1.0/inbox' ),
+);
+*/
+
+// filter output
+$json = \apply_filters( 'activitypub_json_blog_array', $json );
+
+/*
+ * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ */
+\do_action( 'activitypub_json_blog_pre' );
+
+$options = 0;
+// JSON_PRETTY_PRINT added in PHP 5.4
+if ( \get_query_var( 'pretty' ) ) {
+ $options |= \JSON_PRETTY_PRINT; // phpcs:ignore
+}
+
+$options |= \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT;
+
+/*
+ * Options to be passed to json_encode()
+ *
+ * @param int $options The current options flags
+ */
+$options = \apply_filters( 'activitypub_json_blog_options', $options );
+
+\header( 'Content-Type: application/activity+json' );
+echo \wp_json_encode( $json, $options );
+
+/*
+ * Action triggerd after the ActivityPub profile has been created and sent to the client
+ */
+\do_action( 'activitypub_json_blog_post' );
diff --git a/wp-content/plugins/activitypub/templates/json-post.php b/wp-content/plugins/activitypub/templates/post-json.php
similarity index 100%
rename from wp-content/plugins/activitypub/templates/json-post.php
rename to wp-content/plugins/activitypub/templates/post-json.php