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