updated plugin ActivityPub version 0.13.0

This commit is contained in:
KawaiiPunk 2021-07-25 23:24:56 +00:00 committed by Gitium
parent e0e2392c3c
commit e80e4be44b
15 changed files with 567 additions and 111 deletions

View File

@ -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

View File

@ -3,7 +3,7 @@
* Plugin Name: ActivityPub * Plugin Name: ActivityPub
* Plugin URI: https://github.com/pfefferle/wordpress-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. * 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: Matthias Pfefferle
* Author URI: https://notiz.blog/ * Author URI: https://notiz.blog/
* License: MIT * License: MIT
@ -54,7 +54,7 @@ function init() {
\Activitypub\Rest\Webfinger::init(); \Activitypub\Rest\Webfinger::init();
// load NodeInfo endpoints only if blog is public // 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'; require_once \dirname( __FILE__ ) . '/includes/rest/class-nodeinfo.php';
\Activitypub\Rest\NodeInfo::init(); \Activitypub\Rest\NodeInfo::init();
} }
@ -75,6 +75,10 @@ function init() {
\add_filter( 'wp_rest_server_class', function() { \add_filter( 'wp_rest_server_class', function() {
return '\Activitypub\Rest\Server'; return '\Activitypub\Rest\Server';
} ); } );
if ( \WP_DEBUG ) {
require_once \dirname( __FILE__ ) . '/includes/debug.php';
}
} }
\add_action( 'plugins_loaded', '\Activitypub\init' ); \add_action( 'plugins_loaded', '\Activitypub\init' );

View File

@ -34,14 +34,16 @@ class Activitypub {
* @return string The new path to the JSON template. * @return string The new path to the JSON template.
*/ */
public static function render_json_template( $template ) { public static function render_json_template( $template ) {
if ( ! \is_author() && ! \is_singular() ) { if ( ! \is_author() && ! \is_singular() && ! \is_home() ) {
return $template; return $template;
} }
if ( \is_author() ) { if ( \is_author() ) {
$json_template = \dirname( __FILE__ ) . '/../templates/json-author.php'; $json_template = \dirname( __FILE__ ) . '/../templates/author-json.php';
} elseif ( \is_singular() ) { } 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; global $wp_query;

View File

@ -7,27 +7,38 @@ namespace Activitypub;
* @author Matthias Pfefferle * @author Matthias Pfefferle
*/ */
class Health_Check { class Health_Check {
/**
* Initialize health checks
*
* @return void
*/
public static function init() { public static function init() {
\add_filter( 'site_status_tests', array( '\Activitypub\Health_Check', 'add_tests' ) ); \add_filter( 'site_status_tests', array( '\Activitypub\Health_Check', 'add_tests' ) );
} }
public static function add_tests( $tests ) { public static function add_tests( $tests ) {
$tests['direct']['activitypub_test_profile_url'] = array( $tests['direct']['activitypub_test_author_url'] = array(
'label' => \__( 'Profile URL test', 'activitypub' ), 'label' => \__( 'Author URL test', 'activitypub' ),
'test' => array( '\Activitypub\Health_Check', 'test_profile_url' ), 'test' => array( '\Activitypub\Health_Check', 'test_author_url' ),
); );
//$tests['direct']['activitypub_test_profile_url2'] = array( $tests['direct']['activitypub_test_webfinger'] = array(
// 'label' => __( 'Profile URL Test', 'activitypub' ), 'label' => __( 'WebFinger Test', 'activitypub' ),
// 'test' => array( '\Activitypub\Health_Check', 'test_profile_url' ), 'test' => array( '\Activitypub\Health_Check', 'test_webfinger' ),
//); );
return $tests; return $tests;
} }
public static function test_profile_url() { /**
* Author URL tests
*
* @return void
*/
public static function test_author_url() {
$result = array( $result = array(
'label' => \__( 'Profile URL accessible', 'activitypub' ), 'label' => \__( 'Author URL accessible', 'activitypub' ),
'status' => 'good', 'status' => 'good',
'badge' => array( 'badge' => array(
'label' => \__( 'ActivityPub', 'activitypub' ), 'label' => \__( 'ActivityPub', 'activitypub' ),
@ -35,49 +46,245 @@ class Health_Check {
), ),
'description' => \sprintf( 'description' => \sprintf(
'<p>%s</p>', '<p>%s</p>',
\__( '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' => '', 'actions' => '',
'test' => 'test_profile_url', 'test' => 'test_author_url',
); );
$enum = self::is_profile_url_accessible(); $check = self::is_author_url_accessible();
if ( true !== $enum ) { if ( true === $check ) {
$result['status'] = 'critical'; return $result;
$result['label'] = \__( 'Profile URL is not accessible', 'activitypub' );
$result['description'] = \sprintf(
'<p>%s</p>',
\__( 'Authorization Headers are being blocked by your hosting provider. This will cause IndieAuth to fail.', 'activitypub' )
);
} }
$result['status'] = 'critical';
$result['label'] = \__( 'Author URL is not accessible', 'activitypub' );
$result['badge']['color'] = 'red';
$result['description'] = \sprintf(
'<p>%s</p>',
$check->get_error_message()
);
return $result; 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(
'<p>%s</p>',
\__( '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(
'<p>%s</p>',
$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(); $user = \wp_get_current_user();
$author_url = \get_author_posts_url( $user->ID ); $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 // check for "author" in URL
if ( false === \strpos( $author_url, 'author' ) ) { if ( $author_url !== $reference_author_url ) {
return false; return new \WP_Error(
'author_url_not_accessible',
\sprintf(
// translators: %s: Author URL
\__(
'<p>Your author URL <code>%s</code> was replaced, this is often done by plugins.</p>',
'activitypub'
),
$author_url
)
);
} }
// try to access 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 ) ) { if ( \is_wp_error( $response ) ) {
return false; return new \WP_Error(
'author_url_not_accessible',
\sprintf(
// translators: %s: Author URL
\__(
'<p>Your author URL <code>%s</code> 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.</p>',
'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
\__(
'<p>Your author URL <code>%s</code> is redirecting to another page, this is often done by SEO plugins like "Yoast SEO".</p>',
'activitypub'
),
$author_url
)
);
} }
// check if response is JSON // check if response is JSON
$body = \wp_remote_retrieve_body( $response ); $body = \wp_remote_retrieve_body( $response );
if ( ! \is_string( $body ) || ! \is_array( \json_decode( $body, true ) ) ) { 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
\__(
'<p>Your author URL <code>%s</code> does not return valid JSON for <code>application/activity+json</code>. Please check if your hosting supports alternate <code>Accept</code> headers.</p>',
'activitypub'
),
$author_url
)
);
} }
return true; 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
\__(
'<p>Your WebFinger endpoint <code>%s</code> is not accessible. Please check your WordPress setup or permalink structure.</p>',
'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
\__(
'<p>Your WebFinger endpoint <code>%s</code> does not return valid JSON for <code>application/jrd+json</code>.</p>',
'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;
}
} }

View File

@ -0,0 +1,16 @@
<?php
namespace Activitypub;
/**
* Allow localhost URLs if WP_DEBUG is true.
*
* @param array $r Array of HTTP request args.
* @param string $url The request URL.
* @return array $args Array or string of HTTP request arguments.
*/
function allow_localhost( $r, $url ) {
$r['reject_unsafe_urls'] = false;
return $r;
}
add_filter( 'http_request_args', '\Activitypub\allow_localhost', 10, 2 );

View File

@ -14,7 +14,18 @@ function get_context() {
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'PropertyValue' => 'schema:PropertyValue', 'PropertyValue' => 'schema:PropertyValue',
'schema' => 'http://schema.org#', 'schema' => 'http://schema.org#',
'pt' => 'https://joinpeertube.org/ns#',
'toot' => 'http://joinmastodon.org/ns#',
'value' => 'schema:value', 'value' => 'schema:value',
'Hashtag' => 'as:Hashtag',
'featured' => array(
'@id' => 'toot:featured',
'@type' => '@id',
),
'featuredTags' => array(
'@id' => 'toot:featuredTags',
'@type' => '@id',
),
), ),
); );

View File

@ -21,7 +21,7 @@ class Followers {
*/ */
public static function register_routes() { public static function register_routes() {
\register_rest_route( \register_rest_route(
'activitypub/1.0', '/users/(?P<id>\d+)/followers', array( 'activitypub/1.0', '/users/(?P<user_id>\d+)/followers', array(
array( array(
'methods' => \WP_REST_Server::READABLE, 'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Followers', 'get' ), 'callback' => array( '\Activitypub\Rest\Followers', 'get' ),
@ -40,7 +40,7 @@ class Followers {
* @return WP_REST_Response * @return WP_REST_Response
*/ */
public static function get( $request ) { 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 ); $user = \get_user_by( 'ID', $user_id );
if ( ! $user ) { if ( ! $user ) {
@ -61,6 +61,11 @@ class Followers {
$json->{'@context'} = \Activitypub\get_context(); $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->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/followers" ); // phpcs:ignore
$json->totalItems = \Activitypub\count_followers( $user_id ); // phpcs:ignore $json->totalItems = \Activitypub\count_followers( $user_id ); // phpcs:ignore
$json->orderedItems = \Activitypub\Peer\Followers::get_followers( $user_id ); // phpcs:ignore $json->orderedItems = \Activitypub\Peer\Followers::get_followers( $user_id ); // phpcs:ignore
@ -87,7 +92,7 @@ class Followers {
'type' => 'integer', 'type' => 'integer',
); );
$params['id'] = array( $params['user_id'] = array(
'required' => true, 'required' => true,
'type' => 'integer', 'type' => 'integer',
); );

View File

@ -21,7 +21,7 @@ class Following {
*/ */
public static function register_routes() { public static function register_routes() {
\register_rest_route( \register_rest_route(
'activitypub/1.0', '/users/(?P<id>\d+)/following', array( 'activitypub/1.0', '/users/(?P<user_id>\d+)/following', array(
array( array(
'methods' => \WP_REST_Server::READABLE, 'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Following', 'get' ), 'callback' => array( '\Activitypub\Rest\Following', 'get' ),
@ -40,7 +40,7 @@ class Following {
* @return WP_REST_Response * @return WP_REST_Response
*/ */
public static function get( $request ) { 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 ); $user = \get_user_by( 'ID', $user_id );
if ( ! $user ) { if ( ! $user ) {
@ -61,14 +61,17 @@ class Following {
$json->{'@context'} = \Activitypub\get_context(); $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->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/following" ); // phpcs:ignore
$json->totalItems = 0; // 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 = $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 = new \WP_REST_Response( $json, 200 );
$response->header( 'Content-Type', 'application/activity+json' ); $response->header( 'Content-Type', 'application/activity+json' );
@ -87,7 +90,7 @@ class Following {
'type' => 'integer', 'type' => 'integer',
); );
$params['id'] = array( $params['user_id'] = array(
'required' => true, 'required' => true,
'type' => 'integer', 'type' => 'integer',
); );

View File

@ -30,10 +30,15 @@ class Inbox {
'activitypub/1.0', '/inbox', array( 'activitypub/1.0', '/inbox', array(
array( array(
'methods' => \WP_REST_Server::EDITABLE, '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(), 'args' => self::shared_inbox_request_parameters(),
'permission_callback' => '__return_true', '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<user_id>\d+)/inbox', array( 'activitypub/1.0', '/users/(?P<user_id>\d+)/inbox', array(
array( array(
'methods' => \WP_REST_Server::EDITABLE, '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(), 'args' => self::user_inbox_request_parameters(),
'permission_callback' => '__return_true', '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 * Renders the user-inbox
* *
* @param WP_REST_Request $request * @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 * @return WP_REST_Response
*/ */
public static function user_inbox( $request ) { public static function user_inbox_post( $request ) {
$user_id = $request->get_param( 'user_id' ); $user_id = $request->get_param( 'user_id' );
$data = $request->get_params(); $data = $request->get_params();
@ -104,13 +157,28 @@ class Inbox {
* *
* @return WP_REST_Response * @return WP_REST_Response
*/ */
public static function shared_inbox( $request ) { public static function shared_inbox_post( $request ) {
$data = $request->get_params(); $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 ) { foreach ( $users as $user ) {
\do_action( 'activitypub_inbox', $data, $user_id, $type ); \do_action( 'activitypub_inbox', $data, $user->ID, $type );
\do_action( "activitypub_inbox_{$type}", $data, $user_id ); \do_action( "activitypub_inbox_{$type}", $data, $user->ID );
} }
return new \WP_REST_Response( array(), 202 ); return new \WP_REST_Response( array(), 202 );
@ -208,7 +276,7 @@ class Inbox {
); );
$params['to'] = array( $params['to'] = array(
'required' => true, 'required' => false,
'sanitize_callback' => function( $param, $request, $key ) { 'sanitize_callback' => function( $param, $request, $key ) {
if ( \is_string( $param ) ) { if ( \is_string( $param ) ) {
$param = array( $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 // do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' ); \add_filter( 'pre_option_require_name_email', '__return_false' );
$state = \wp_new_comment( $commentdata, true ); $state = \wp_new_comment( $commentdata, true );
\remove_filter( 'pre_option_require_name_email', '__return_false' ); \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 // do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' ); \add_filter( 'pre_option_require_name_email', '__return_false' );
$state = \wp_new_comment( $commentdata, true ); $state = \wp_new_comment( $commentdata, true );
\remove_filter( 'pre_option_require_name_email', '__return_false' ); \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;
} }
} }

View File

@ -21,10 +21,10 @@ class Outbox {
*/ */
public static function register_routes() { public static function register_routes() {
\register_rest_route( \register_rest_route(
'activitypub/1.0', '/users/(?P<id>\d+)/outbox', array( 'activitypub/1.0', '/users/(?P<user_id>\d+)/outbox', array(
array( array(
'methods' => \WP_REST_Server::READABLE, 'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox' ), 'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox_get' ),
'args' => self::request_parameters(), 'args' => self::request_parameters(),
'permission_callback' => '__return_true', 'permission_callback' => '__return_true',
), ),
@ -38,8 +38,8 @@ class Outbox {
* @param WP_REST_Request $request * @param WP_REST_Request $request
* @return WP_REST_Response * @return WP_REST_Response
*/ */
public static function user_outbox( $request ) { public static function user_outbox_get( $request ) {
$user_id = $request->get_param( 'id' ); $user_id = $request->get_param( 'user_id' );
$author = \get_user_by( 'ID', $user_id ); $author = \get_user_by( 'ID', $user_id );
if ( ! $author ) { if ( ! $author ) {
@ -70,24 +70,27 @@ class Outbox {
$count_posts = \wp_count_posts(); $count_posts = \wp_count_posts();
$json->totalItems = \intval( $count_posts->publish ); // phpcs:ignore $json->totalItems = \intval( $count_posts->publish ); // phpcs:ignore
$posts = \get_posts( array( $json->first = \add_query_arg( 'page', 1, $json->partOf ); // phpcs:ignore
'posts_per_page' => 10, $json->last = \add_query_arg( 'page', \ceil ( $json->totalItems / 10 ), $json->partOf ); // phpcs:ignore
'author' => $user_id,
'offset' => $page * 10,
) );
$json->first = \add_query_arg( 'page', 0, $json->partOf ); // phpcs:ignore if ( $page && ( ( \ceil ( $json->totalItems / 10 ) ) > $page ) ) { // phpcs:ignore
$json->last = \add_query_arg( 'page', ( \ceil ( $json->totalItems / 10 ) ) - 1, $json->partOf ); // phpcs:ignore $json->next = \add_query_arg( 'page', $page + 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
} }
foreach ( $posts as $post ) { if ( $page ) {
$activitypub_post = new \Activitypub\Model\Post( $post ); $posts = \get_posts( array(
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_NONE ); 'posts_per_page' => 10,
$activitypub_activity->from_post( $activitypub_post->to_array() ); 'author' => $user_id,
$json->orderedItems[] = $activitypub_activity->to_array(); // phpcs:ignore '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 // filter output
@ -117,7 +120,7 @@ class Outbox {
'type' => 'integer', 'type' => 'integer',
); );
$params['id'] = array( $params['user_id'] = array(
'required' => true, 'required' => true,
'type' => 'integer', 'type' => 'integer',
); );

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -3,8 +3,8 @@ Contributors: pfefferle, mediaformat
Donate link: https://notiz.blog/donate/ Donate link: https://notiz.blog/donate/
Tags: OStatus, fediverse, activitypub, activitystream Tags: OStatus, fediverse, activitypub, activitystream
Requires at least: 4.7 Requires at least: 4.7
Tested up to: 5.6 Tested up to: 5.8
Stable tag: 0.12.0 Stable tag: 0.13.0
Requires PHP: 5.6 Requires PHP: 5.6
License: MIT License: MIT
License URI: http://opensource.org/licenses/MIT License URI: http://opensource.org/licenses/MIT
@ -49,7 +49,7 @@ To implement:
= What is "ActivityPub for WordPress" = = 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? = = 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). 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 = = 0.12.0 =
* use "pre_option_require_name_email" filter instead of "check_comment_flood". props [@akirk](https://github.com/akirk) * use "pre_option_require_name_email" filter instead of "check_comment_flood". props [@akirk](https://github.com/akirk)

View File

@ -82,12 +82,12 @@ $json->endpoints = array(
*/ */
// filter output // 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 * 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; $options = 0;
// JSON_PRETTY_PRINT added in PHP 5.4 // 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 * @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' ); \header( 'Content-Type: application/activity+json' );
echo \wp_json_encode( $json, $options ); 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 * 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 );

View File

@ -0,0 +1,92 @@
<?php
$json = new \stdClass();
$json->{'@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(
'<a rel="me" title="' . \esc_attr( \home_url( '/' ) ) . '" target="_blank" href="' . \home_url( '/' ) . '">' . \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) . '</a>',
\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' );