diff --git a/activitypub.php b/activitypub.php
index 8f665fc..0f9e8d3 100644
--- a/activitypub.php
+++ b/activitypub.php
@@ -39,6 +39,7 @@ function init() {
Collection\Followers::init();
// Configure the REST API route
+ Rest\User::init();
Rest\Outbox::init();
Rest\Inbox::init();
Rest\Followers::init();
diff --git a/assets/css/activitypub-admin.css b/assets/css/activitypub-admin.css
index cd1808c..8925bfd 100644
--- a/assets/css/activitypub-admin.css
+++ b/assets/css/activitypub-admin.css
@@ -4,6 +4,10 @@
margin-top: 10px;
}
+.settings_page_activitypub .wrap {
+ padding-left: 22px;
+}
+
.activitypub-settings-header {
text-align: center;
margin: 0 0 1rem;
@@ -28,7 +32,7 @@
-ms-grid-columns: 1fr 1fr;
vertical-align: top;
display: inline-grid;
- grid-template-columns: 1fr 1fr;
+ grid-template-columns: 1fr 1fr 1fr;
}
.activitypub-settings-tab.active {
diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php
index 55329b3..6ebfcbf 100644
--- a/includes/class-activitypub.php
+++ b/includes/class-activitypub.php
@@ -224,6 +224,11 @@ class Activitypub {
'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/nodeinfo2',
'top'
);
+ \add_rewrite_rule(
+ '^@([\w]+)',
+ 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/users/$matches[1]',
+ 'top'
+ );
}
\add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
diff --git a/includes/class-admin.php b/includes/class-admin.php
index 7b62a08..b1b5bc7 100644
--- a/includes/class-admin.php
+++ b/includes/class-admin.php
@@ -13,6 +13,10 @@ class Admin {
* Initialize the class, registering WordPress hooks
*/
public static function init() {
+ if ( ! current_user_can( 'publish_posts' ) ) {
+ return;
+ }
+
\add_action( 'admin_menu', array( self::class, 'admin_menu' ) );
\add_action( 'admin_init', array( self::class, 'register_settings' ) );
\add_action( 'show_user_profile', array( self::class, 'add_profile' ) );
@@ -24,6 +28,11 @@ class Admin {
* Add admin menu entry
*/
public static function admin_menu() {
+ // user has to be able to publish posts
+ if ( ! current_user_can( 'publish_posts' ) ) {
+ return;
+ }
+
$settings_page = \add_options_page(
'Welcome',
'ActivityPub',
@@ -55,6 +64,9 @@ class Admin {
case 'settings':
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/settings.php' );
break;
+ case 'followers':
+ \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/followers-list.php' );
+ break;
case 'welcome':
default:
wp_enqueue_script( 'plugin-install' );
@@ -70,6 +82,9 @@ class Admin {
* Load user settings page
*/
public static function followers_list_page() {
+ if ( ! current_user_can( 'publish_posts' ) ) {
+ return;
+ }
\load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/followers-list.php' );
}
diff --git a/includes/class-user-factory.php b/includes/class-user-factory.php
new file mode 100644
index 0000000..3ae3237
--- /dev/null
+++ b/includes/class-user-factory.php
@@ -0,0 +1,157 @@
+ 404 )
+ );
+ }
+
+ return new User( $user->ID );
+ }
+ }
+
+ /**
+ * Get the User by username.
+ *
+ * @param string $username The User-Name.
+ *
+ * @return \Acitvitypub\Model\User The User.
+ */
+ public static function get_by_username( $username ) {
+ // check for blog user.
+ if ( get_option( 'activitypub_blog_identifier', null ) === $username ) {
+ return self::get_by_id( self::BLOG_USER_ID );
+ }
+
+ // check for 'activitypub_username' meta
+ $user = new WP_User_Query(
+ array(
+ 'number' => 1,
+ 'hide_empty' => true,
+ 'fields' => 'ID',
+ 'meta_query' => array(
+ 'relation' => 'OR',
+ array(
+ 'key' => 'activitypub_identifier',
+ 'value' => $username,
+ 'compare' => 'LIKE',
+ ),
+ ),
+ )
+ );
+
+ if ( $user->results ) {
+ return self::get_by_id( $user->results[0] );
+ }
+
+ // check for login or nicename.
+ $user = new WP_User_Query(
+ array(
+ 'search' => $username,
+ 'search_columns' => array( 'user_login', 'user_nicename' ),
+ 'number' => 1,
+ 'hide_empty' => true,
+ 'fields' => 'ID',
+ )
+ );
+
+ if ( $user->results ) {
+ return self::get_by_id( $user->results[0] );
+ }
+
+ return new WP_Error(
+ 'activitypub_user_not_found',
+ \__( 'User not found', 'activitypub' ),
+ array( 'status' => 404 )
+ );
+ }
+
+ /**
+ * Get the User by resource.
+ *
+ * @param string $resource The User-Resource.
+ *
+ * @return \Acitvitypub\Model\User The User.
+ */
+ public static function get_by_resource( $resource ) {
+ if ( \strpos( $resource, '@' ) === false ) {
+ return new WP_Error(
+ 'activitypub_unsupported_resource',
+ \__( 'Resource is invalid', 'activitypub' ),
+ array( 'status' => 400 )
+ );
+ }
+
+ $resource = \str_replace( 'acct:', '', $resource );
+
+ $resource_identifier = \substr( $resource, 0, \strrpos( $resource, '@' ) );
+ $resource_host = \str_replace( 'www.', '', \substr( \strrchr( $resource, '@' ), 1 ) );
+ $blog_host = \str_replace( 'www.', '', \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) );
+
+ if ( $blog_host !== $resource_host ) {
+ return new WP_Error(
+ 'activitypub_wrong_host',
+ \__( 'Resource host does not match blog host', 'activitypub' ),
+ array( 'status' => 404 )
+ );
+ }
+
+ return self::get_by_username( $resource_identifier );
+ }
+
+ /**
+ * Get the User by resource.
+ *
+ * @param string $resource The User-Resource.
+ *
+ * @return \Acitvitypub\Model\User The User.
+ */
+ public static function get_by_various( $id ) {
+ if ( is_numeric( $id ) ) {
+ return self::get_by_id( $id );
+ } elseif ( filter_var( $id, FILTER_VALIDATE_URL ) ) {
+ return self::get_by_resource( $id );
+ } else {
+ return self::get_by_username( $id );
+ }
+ }
+}
diff --git a/includes/functions.php b/includes/functions.php
index 2a17bfa..8811d9b 100644
--- a/includes/functions.php
+++ b/includes/functions.php
@@ -7,27 +7,7 @@ namespace Activitypub;
* @return array the activitypub context
*/
function get_context() {
- $context = array(
- 'https://www.w3.org/ns/activitystreams',
- 'https://w3id.org/security/v1',
- array(
- 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
- 'PropertyValue' => 'schema:PropertyValue',
- 'schema' => 'http://schema.org#',
- 'pt' => 'https://joinpeertube.org/ns#',
- 'toot' => 'http://joinmastodon.org/ns#',
- 'value' => 'schema:value',
- 'Hashtag' => 'as:Hashtag',
- 'featured' => array(
- '@id' => 'toot:featured',
- '@type' => '@id',
- ),
- 'featuredTags' => array(
- '@id' => 'toot:featuredTags',
- '@type' => '@id',
- ),
- ),
- );
+ $context = Model\Activity::CONTEXT;
return \apply_filters( 'activitypub_json_context', $context );
}
@@ -187,21 +167,6 @@ function url_to_authorid( $url ) {
return 0;
}
-/**
- * Return the custom Activity Pub description, if set, or default author description.
- *
- * @param int $user_id The user ID.
- *
- * @return string The author description.
- */
-function get_author_description( $user_id ) {
- $description = get_user_meta( $user_id, 'activitypub_user_description', true );
- if ( empty( $description ) ) {
- $description = get_user_meta( $user_id, 'description', true );
- }
- return \wpautop( \wp_kses( $description, 'default' ) );
-}
-
/**
* Check for Tombstone Objects
*
diff --git a/includes/model/class-activity.php b/includes/model/class-activity.php
index bf06bc1..bef7f1e 100644
--- a/includes/model/class-activity.php
+++ b/includes/model/class-activity.php
@@ -11,12 +11,8 @@ use function Activitypub\get_rest_url_by_path;
* @see https://www.w3.org/TR/activitypub/
*/
class Activity {
- /**
- * The JSON-LD context.
- *
- * @var array
- */
- private $context = array(
+
+ const CONTEXT = array(
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
array(
@@ -38,6 +34,13 @@ class Activity {
),
);
+ /**
+ * The JSON-LD context.
+ *
+ * @var array
+ */
+ private $context = self::CONTEXT;
+
/**
* The published date.
*
diff --git a/includes/model/class-application-user.php b/includes/model/class-application-user.php
new file mode 100644
index 0000000..c800531
--- /dev/null
+++ b/includes/model/class-application-user.php
@@ -0,0 +1,39 @@
+ 'date',
+ 'order' => 'ASC',
+ 'number' => 1,
+ )
+ );
+
+ if ( ! empty( $first_post->posts[0] ) ) {
+ $time = \strtotime( $first_post->posts[0]->post_date_gmt );
+ } else {
+ $time = \time();
+ }
+
+ return \gmdate( 'Y-m-d\TH:i:s\Z', $time );
+ }
+}
diff --git a/includes/model/class-user.php b/includes/model/class-user.php
index 5c62473..ba24a66 100644
--- a/includes/model/class-user.php
+++ b/includes/model/class-user.php
@@ -1,23 +1,242 @@
user_id = $user_id;
+
+ add_filter( 'activitypub_json_author_array', array( $this, 'add_api_endpoints' ), 10, 2 );
+ add_filter( 'activitypub_json_author_array', array( $this, 'add_attachments' ), 10, 2 );
+ }
+
+ /**
+ * Magic function to implement getter and setter
+ *
+ * @param string $method
+ * @param string $params
+ *
+ * @return void
+ */
+ public function __call( $method, $params ) {
+ $var = \strtolower( \substr( $method, 4 ) );
+
+ if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
+ return $this->$var;
+ }
+
+ if ( \strncasecmp( $method, 'has', 3 ) === 0 ) {
+ return (bool) call_user_func( 'get_' . $var, $this );
+ }
+ }
+
+ /**
+ * Get the User-ID.
+ *
+ * @return string The User-ID.
+ */
+ public function get_id() {
+ return $this->get_url();
+ }
+
+ /**
+ * Get the User-Name.
+ *
+ * @return string The User-Name.
+ */
+ public function get_name() {
+ return \esc_attr( \get_the_author_meta( 'display_name', $this->user_id ) );
+ }
+
+ /**
+ * Get the User-Description.
+ *
+ * @return string The User-Description.
+ */
+ public function get_summary() {
+ $description = get_user_meta( $this->user_id, 'activitypub_user_description', true );
+ if ( empty( $description ) ) {
+ $description = get_user_meta( $this->user_id, 'description', true );
+ }
+ return \wpautop( \wp_kses( $description, 'default' ) );
+ }
+
+ /**
+ * Get the User-Url.
+ *
+ * @return string The User-Url.
+ */
+ public function get_url() {
+ return \esc_url( \get_author_posts_url( $this->user_id ) );
+ }
+
+ public function get_username() {
+ return \esc_attr( \get_the_author_meta( 'login', $this->user_id ) );
+ }
+
+ public function get_avatar() {
+ return \esc_url(
+ \get_avatar_url(
+ $this->user_id,
+ array( 'size' => 120 )
+ )
+ );
+ }
+
+ public function get_header_image() {
+ if ( \has_header_image() ) {
+ return \esc_url( \get_header_image() );
+ }
+
+ return null;
+ }
+
+ public function get_published() {
+ return \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( \get_the_author_meta( 'registered', $this->user_id ) ) );
+ }
+
+ public function get_public_key() {
+ //return Signature::get_public_key( $this->user_id );
+ return null;
+ }
+
+ /**
+ * Array representation of the User.
+ *
+ * @param bool $context Whether to include the @context.
+ *
+ * @return array The array representation of the User.
+ */
+ public function to_array( $context = true ) {
+ $output = array();
+
+ if ( $context ) {
+ $output['@context'] = Activity::CONTEXT;
+ }
+
+ $output['id'] = $this->get_url();
+ $output['type'] = $this->get_type();
+ $output['name'] = $this->get_name();
+ $output['summary'] = \html_entity_decode(
+ $this->get_summary(),
+ \ENT_QUOTES,
+ 'UTF-8'
+ );
+ $output['preferredUsername'] = $this->get_username(); // phpcs:ignore
+ $output['url'] = $this->get_url();
+ $output['icon'] = array(
+ 'type' => 'Image',
+ 'url' => $this->get_avatar(),
+ );
+
+ if ( $this->has_header_image() ) {
+ $output['image'] = array(
+ 'type' => 'Image',
+ 'url' => $this->get_header_image(),
+ );
+ }
+
+ $output['published'] = $this->get_published();
+
+ $output['publicKey'] = array(
+ 'id' => $this->get_url() . '#main-key',
+ 'owner' => $this->get_url(),
+ 'publicKeyPem' => \trim( $this->get_public_key() ),
+ );
+
+ $output['manuallyApprovesFollowers'] = \apply_filters( 'activitypub_json_manually_approves_followers', \__return_false() ); // phpcs:ignore
+
+ // filter output
+ $output = \apply_filters( 'activitypub_json_author_array', $output, $this->user_id, $this );
+
+ return $output;
+ }
+
+ /**
+ * Extend the User-Output with API-Endpoints.
+ *
+ * @param array $array The User-Output.
+ * @param numeric $user_id The User-ID.
+ *
+ * @return array The extended User-Output.
+ */
+ public function add_api_endpoints( $array, $user_id ) {
+ $array['inbox'] = get_rest_url_by_path( sprintf( 'users/%d/inbox', $user_id ) );
+ $array['outbox'] = get_rest_url_by_path( sprintf( 'users/%d/outbox', $user_id ) );
+ $array['followers'] = get_rest_url_by_path( sprintf( 'users/%d/followers', $user_id ) );
+ $array['following'] = get_rest_url_by_path( sprintf( 'users/%d/following', $user_id ) );
+
+ return $array;
+ }
+
+ /**
+ * Extend the User-Output with Attachments.
+ *
+ * @param array $array The User-Output.
+ * @param numeric $user_id The User-ID.
+ *
+ * @return array The extended User-Output.
+ */
+ public function add_attachments( $array, $user_id ) {
+ $array['attachment'] = array();
+
+ $array['attachment']['blog_url'] = array(
+ 'type' => 'PropertyValue',
+ 'name' => \__( 'Blog', 'activitypub' ),
+ 'value' => \html_entity_decode(
+ '' . \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) . '',
+ \ENT_QUOTES,
+ 'UTF-8'
+ ),
+ );
+
+ $array['attachment']['profile_url'] = array(
+ 'type' => 'PropertyValue',
+ 'name' => \__( 'Profile', 'activitypub' ),
+ 'value' => \html_entity_decode(
+ '' . \wp_parse_url( \get_author_posts_url( $user_id ), \PHP_URL_HOST ) . '',
+ \ENT_QUOTES,
+ 'UTF-8'
+ ),
+ );
+
+ if ( \get_the_author_meta( 'user_url', $user_id ) ) {
+ $array['attachment']['user_url'] = array(
+ 'type' => 'PropertyValue',
+ 'name' => \__( 'Website', 'activitypub' ),
+ 'value' => \html_entity_decode(
+ '' . \wp_parse_url( \get_the_author_meta( 'user_url', $user_id ), \PHP_URL_HOST ) . '',
+ \ENT_QUOTES,
+ 'UTF-8'
+ ),
+ );
+ }
+
+ return $array;
+ }
}
diff --git a/includes/rest/class-followers.php b/includes/rest/class-followers.php
index 30509be..233c4ed 100644
--- a/includes/rest/class-followers.php
+++ b/includes/rest/class-followers.php
@@ -5,6 +5,7 @@ use WP_Error;
use stdClass;
use WP_REST_Server;
use WP_REST_Response;
+use Activitypub\User_Factory;
use Activitypub\Collection\Followers as FollowerCollection;
use function Activitypub\get_rest_url_by_path;
@@ -30,7 +31,7 @@ class Followers {
public static function register_routes() {
\register_rest_route(
ACTIVITYPUB_REST_NAMESPACE,
- '/users/(?P\d+)/followers',
+ '/users/(?P\w+)/followers',
array(
array(
'methods' => WP_REST_Server::READABLE,
@@ -51,19 +52,10 @@ class Followers {
*/
public static function get( $request ) {
$user_id = $request->get_param( 'user_id' );
- $user = \get_user_by( 'ID', $user_id );
+ $user = User_Factory::get_by_various( $user_id );
- if ( ! $user ) {
- return new WP_Error(
- 'rest_invalid_param',
- \__( 'User not found', 'activitypub' ),
- array(
- 'status' => 404,
- 'params' => array(
- 'user_id' => \__( 'User not found', 'activitypub' ),
- ),
- )
- );
+ if ( is_wp_error( $user ) ) {
+ return $user;
}
/*
@@ -77,18 +69,18 @@ class Followers {
$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->actor = $user->get_id();
$json->type = 'OrderedCollectionPage';
- $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/followers', $user_id ) ); // phpcs:ignore
+ $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/followers', $user->get_user_id() ) ); // phpcs:ignore
$json->first = $json->partOf; // phpcs:ignore
- $json->totalItems = FollowerCollection::count_followers( $user_id ); // phpcs:ignore
+ $json->totalItems = FollowerCollection::count_followers( $user->get_user_id() ); // phpcs:ignore
// phpcs:ignore
$json->orderedItems = array_map(
function( $item ) {
return $item->get_url();
},
- FollowerCollection::get_followers( $user_id )
+ FollowerCollection::get_followers( $user->get_user_id() )
);
$response = new WP_REST_Response( $json, 200 );
@@ -111,10 +103,7 @@ class Followers {
$params['user_id'] = array(
'required' => true,
- 'type' => 'integer',
- 'validate_callback' => function( $param, $request, $key ) {
- return user_can( $param, 'publish_posts' );
- },
+ 'type' => 'string',
);
return $params;
diff --git a/includes/rest/class-following.php b/includes/rest/class-following.php
index 93ad6a7..416d3a4 100644
--- a/includes/rest/class-following.php
+++ b/includes/rest/class-following.php
@@ -1,6 +1,8 @@
\d+)/following',
+ '/users/(?P\w+)/following',
array(
array(
'methods' => \WP_REST_Server::READABLE,
@@ -45,19 +47,10 @@ class Following {
*/
public static function get( $request ) {
$user_id = $request->get_param( 'user_id' );
- $user = \get_user_by( 'ID', $user_id );
+ $user = User_Factory::get_by_various( $user_id );
- if ( ! $user ) {
- return new \WP_Error(
- 'rest_invalid_param',
- \__( 'User not found', 'activitypub' ),
- array(
- 'status' => 404,
- 'params' => array(
- 'user_id' => \__( 'User not found', 'activitypub' ),
- ),
- )
- );
+ if ( is_wp_error( $user ) ) {
+ return $user;
}
/*
@@ -71,10 +64,10 @@ class Following {
$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->actor = $user->get_id();
$json->type = 'OrderedCollectionPage';
- $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/following', $user_id ) ); // phpcs:ignore
+ $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/following', $user->get_user_id() ) ); // phpcs:ignore
$json->totalItems = 0; // phpcs:ignore
$json->orderedItems = apply_filters( 'activitypub_following', array(), $user ); // phpcs:ignore
@@ -100,10 +93,7 @@ class Following {
$params['user_id'] = array(
'required' => true,
- 'type' => 'integer',
- 'validate_callback' => function( $param, $request, $key ) {
- return user_can( $param, 'publish_posts' );
- },
+ 'type' => 'string',
);
return $params;
diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php
index 76ce57c..8e77edb 100644
--- a/includes/rest/class-inbox.php
+++ b/includes/rest/class-inbox.php
@@ -4,7 +4,7 @@ namespace Activitypub\Rest;
use WP_Error;
use WP_REST_Server;
use WP_REST_Response;
-use Activitypub\Signature;
+use Activitypub\User_Factory;
use Activitypub\Model\Activity;
use function Activitypub\get_context;
@@ -48,7 +48,7 @@ class Inbox {
\register_rest_route(
ACTIVITYPUB_REST_NAMESPACE,
- '/users/(?P\d+)/inbox',
+ '/users/(?P\w+)/inbox',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
@@ -74,6 +74,12 @@ class Inbox {
*/
public static function user_inbox_get( $request ) {
$user_id = $request->get_param( 'user_id' );
+ $user = User_Factory::get_by_various( $user_id );
+
+ if ( is_wp_error( $user ) ) {
+ return $user;
+ }
+
$page = $request->get_param( 'page', 0 );
/*
@@ -87,7 +93,7 @@ class Inbox {
$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_by_path( sprintf( 'users/%d/inbox', $user_id ) ); // phpcs:ignore
+ $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/inbox', $user->get_user_id() ) ); // phpcs:ignore
$json->totalItems = 0; // phpcs:ignore
@@ -120,13 +126,18 @@ class Inbox {
public static function user_inbox_post( $request ) {
$user_id = $request->get_param( 'user_id' );
+ $user = User_Factory::get_by_various( $user_id );
+
+ if ( is_wp_error( $user ) ) {
+ return $user;
+ }
$data = $request->get_params();
$type = $request->get_param( 'type' );
$type = \strtolower( $type );
- \do_action( 'activitypub_inbox', $data, $user_id, $type );
- \do_action( "activitypub_inbox_{$type}", $data, $user_id );
+ \do_action( 'activitypub_inbox', $data, $user->get_user_id(), $type );
+ \do_action( "activitypub_inbox_{$type}", $data, $user->get_user_id() );
return new WP_REST_Response( array(), 202 );
}
@@ -185,10 +196,7 @@ class Inbox {
$params['user_id'] = array(
'required' => true,
- 'type' => 'integer',
- 'validate_callback' => function( $param, $request, $key ) {
- return user_can( $param, 'publish_posts' );
- },
+ 'type' => 'string',
);
return $params;
@@ -208,10 +216,7 @@ class Inbox {
$params['user_id'] = array(
'required' => true,
- 'type' => 'integer',
- 'validate_callback' => function( $param, $request, $key ) {
- return user_can( $param, 'publish_posts' );
- },
+ 'type' => 'string',
);
$params['id'] = array(
diff --git a/includes/rest/class-outbox.php b/includes/rest/class-outbox.php
index 2138f71..d7e730f 100644
--- a/includes/rest/class-outbox.php
+++ b/includes/rest/class-outbox.php
@@ -5,6 +5,7 @@ use stdClass;
use WP_Error;
use WP_REST_Server;
use WP_REST_Response;
+use Activitypub\User_Factory;
use Activitypub\Model\Post;
use Activitypub\Model\Activity;
@@ -32,7 +33,7 @@ class Outbox {
public static function register_routes() {
\register_rest_route(
ACTIVITYPUB_REST_NAMESPACE,
- '/users/(?P\d+)/outbox',
+ '/users/(?P\w+)/outbox',
array(
array(
'methods' => WP_REST_Server::READABLE,
@@ -52,22 +53,14 @@ class Outbox {
*/
public static function user_outbox_get( $request ) {
$user_id = $request->get_param( 'user_id' );
- $author = \get_user_by( 'ID', $user_id );
- $post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) );
+ $user = User_Factory::get_by_various( $user_id );
- if ( ! $author ) {
- return new WP_Error(
- 'rest_invalid_param',
- \__( 'User not found', 'activitypub' ),
- array(
- 'status' => 404,
- 'params' => array(
- 'user_id' => \__( 'User not found', 'activitypub' ),
- ),
- )
- );
+ if ( is_wp_error( $user ) ) {
+ return $user;
}
+ $post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) );
+
$page = $request->get_param( 'page', 0 );
/*
@@ -80,9 +73,9 @@ class Outbox {
$json->{'@context'} = 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->actor = $user->get_id();
$json->type = 'OrderedCollectionPage';
- $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/outbox', $user_id ) ); // phpcs:ignore
+ $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/outbox', $user->get_user_id() ) ); // phpcs:ignore
$json->totalItems = 0; // phpcs:ignore
// phpcs:ignore
@@ -104,7 +97,7 @@ class Outbox {
$posts = \get_posts(
array(
'posts_per_page' => 10,
- 'author' => $user_id,
+ 'author' => $user->get_user_id(),
'offset' => ( $page - 1 ) * 10,
'post_type' => $post_types,
)
@@ -148,10 +141,7 @@ class Outbox {
$params['user_id'] = array(
'required' => true,
- 'type' => 'integer',
- 'validate_callback' => function( $param, $request, $key ) {
- return user_can( $param, 'publish_posts' );
- },
+ 'type' => 'string',
);
return $params;
diff --git a/includes/rest/class-user.php b/includes/rest/class-user.php
new file mode 100644
index 0000000..4bb3783
--- /dev/null
+++ b/includes/rest/class-user.php
@@ -0,0 +1,89 @@
+\w+)',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( self::class, 'get' ),
+ 'args' => self::request_parameters(),
+ 'permission_callback' => '__return_true',
+ ),
+ )
+ );
+ }
+
+ /**
+ * Handle GET request
+ *
+ * @param WP_REST_Request $request
+ *
+ * @return WP_REST_Response
+ */
+ public static function get( $request ) {
+ $user_id = $request->get_param( 'user_id' );
+ $user = User_Factory::get_by_various( $user_id );
+
+ if ( is_wp_error( $user ) ) {
+ return $user;
+ }
+
+ /*
+ * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ */
+ \do_action( 'activitypub_outbox_pre' );
+
+ $json = $user->to_array();
+
+ $response = new WP_REST_Response( $json, 200 );
+ $response->header( 'Content-Type', 'application/activity+json' );
+
+ return $response;
+ }
+
+ /**
+ * The supported parameters
+ *
+ * @return array list of parameters
+ */
+ public static function request_parameters() {
+ $params = array();
+
+ $params['page'] = array(
+ 'type' => 'string',
+ );
+
+ $params['user_id'] = array(
+ 'required' => true,
+ 'type' => 'string',
+ );
+
+ return $params;
+ }
+}
diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php
index f75a3f7..4f1d5cf 100644
--- a/includes/rest/class-webfinger.php
+++ b/includes/rest/class-webfinger.php
@@ -3,6 +3,7 @@ namespace Activitypub\Rest;
use WP_Error;
use WP_REST_Response;
+use Activitypub\User_Factory;
/**
* ActivityPub WebFinger REST-Class
@@ -47,41 +48,27 @@ class Webfinger {
public static function webfinger( $request ) {
$resource = $request->get_param( 'resource' );
- if ( \strpos( $resource, '@' ) === false ) {
- return new WP_Error( 'activitypub_unsupported_resource', \__( 'Resource is invalid', 'activitypub' ), array( 'status' => 400 ) );
- }
+ $user = User_Factory::get_by_resource( $resource );
- $resource = \str_replace( 'acct:', '', $resource );
-
- $resource_identifier = \substr( $resource, 0, \strrpos( $resource, '@' ) );
- $resource_host = \str_replace( 'www.', '', \substr( \strrchr( $resource, '@' ), 1 ) );
- $blog_host = \str_replace( 'www.', '', \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) );
-
- if ( $blog_host !== $resource_host ) {
- return new WP_Error( 'activitypub_wrong_host', \__( 'Resource host does not match blog host', 'activitypub' ), array( 'status' => 404 ) );
- }
-
- $user = \get_user_by( 'login', \esc_sql( $resource_identifier ) );
-
- if ( ! $user || ! \user_can( $user, 'publish_posts' ) ) {
- return new WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) );
+ if ( is_wp_error( $user ) ) {
+ return $user;
}
$json = array(
'subject' => $resource,
'aliases' => array(
- \get_author_posts_url( $user->ID ),
+ $user->get_url(),
),
'links' => array(
array(
'rel' => 'self',
'type' => 'application/activity+json',
- 'href' => \get_author_posts_url( $user->ID ),
+ 'href' => $user->get_url(),
),
array(
'rel' => 'http://webfinger.net/rel/profile-page',
'type' => 'text/html',
- 'href' => \get_author_posts_url( $user->ID ),
+ 'href' => $user->get_url(),
),
),
);
diff --git a/includes/table/class-followers.php b/includes/table/class-followers.php
index 246cc0b..5f1c199 100644
--- a/includes/table/class-followers.php
+++ b/includes/table/class-followers.php
@@ -9,6 +9,24 @@ if ( ! \class_exists( '\WP_List_Table' ) ) {
}
class Followers extends WP_List_Table {
+ private $user_id;
+
+ public function __construct() {
+ if ( get_current_screen()->id === 'settings_page_activitypub' ) {
+ $this->user_id = -1;
+ } else {
+ $this->user_id = \get_current_user_id();
+ }
+
+ parent::__construct(
+ array(
+ 'singular' => \__( 'Follower', 'activitypub' ),
+ 'plural' => \__( 'Followers', 'activitypub' ),
+ 'ajax' => false,
+ )
+ );
+ }
+
public function get_columns() {
return array(
'cb' => '',
@@ -36,8 +54,8 @@ class Followers extends WP_List_Table {
$page_num = $this->get_pagenum();
$per_page = 20;
- $followers = FollowerCollection::get_followers( \get_current_user_id(), $per_page, ( $page_num - 1 ) * $per_page );
- $counter = FollowerCollection::count_followers( \get_current_user_id() );
+ $follower = FollowerCollection::get_followers( $this->user_id, $per_page, ( $page_num - 1 ) * $per_page );
+ $counter = FollowerCollection::count_followers( $this->user_id );
$this->items = array();
$this->set_pagination_args(
@@ -104,7 +122,7 @@ class Followers extends WP_List_Table {
return false;
}
- if ( ! current_user_can( 'edit_user', \get_current_user_id() ) ) {
+ if ( ! current_user_can( 'edit_user', $this->user_id ) ) {
return false;
}
@@ -121,4 +139,8 @@ class Followers extends WP_List_Table {
break;
}
}
+
+ public function get_user_count() {
+ return FollowerCollection::count_followers( $this->user_id );
+ }
}
diff --git a/templates/admin-header.php b/templates/admin-header.php
index 73a830b..974b224 100644
--- a/templates/admin-header.php
+++ b/templates/admin-header.php
@@ -11,6 +11,10 @@
+
+
+
+
diff --git a/templates/author-json.php b/templates/author-json.php
index 7b112ac..aa38b97 100644
--- a/templates/author-json.php
+++ b/templates/author-json.php
@@ -1,92 +1,10 @@
{'@context'} = \Activitypub\get_context();
-$json->id = \get_author_posts_url( $author_id );
-$json->type = 'Person';
-$json->name = \get_the_author_meta( 'display_name', $author_id );
-$json->summary = \html_entity_decode(
- \Activitypub\get_author_description( $author_id ),
- \ENT_QUOTES,
- 'UTF-8'
-);
-$json->preferredUsername = \get_the_author_meta( 'login', $author_id ); // phpcs:ignore
-$json->url = \get_author_posts_url( $author_id );
-$json->icon = array(
- 'type' => 'Image',
- 'url' => \get_avatar_url( $author_id, array( 'size' => 120 ) ),
-);
-
-$json->published = \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( \get_the_author_meta( 'registered', $author_id ) ) );
-
-if ( \has_header_image() ) {
- $json->image = array(
- 'type' => 'Image',
- 'url' => \get_header_image(),
- );
-}
-
-$json->inbox = \Activitypub\get_rest_url_by_path( sprintf( 'users/%d/inbox', $author_id ) );
-$json->outbox = \Activitypub\get_rest_url_by_path( sprintf( 'users/%d/outbox', $author_id ) );
-$json->followers = \Activitypub\get_rest_url_by_path( sprintf( 'users/%d/followers', $author_id ) );
-$json->following = \Activitypub\get_rest_url_by_path( sprintf( 'users/%d/following', $author_id ) );
-
-$json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_approves_followers', \__return_false() ); // phpcs:ignore
-
-// phpcs:ignore
-$json->publicKey = array(
- 'id' => \get_author_posts_url( $author_id ) . '#main-key',
- 'owner' => \get_author_posts_url( $author_id ),
- 'publicKeyPem' => \trim( \Activitypub\Signature::get_public_key( $author_id ) ),
-);
-
-$json->tag = array();
-$json->attachment = array();
-
-$json->attachment['blog_url'] = array(
- 'type' => 'PropertyValue',
- 'name' => \__( 'Blog', 'activitypub' ),
- 'value' => \html_entity_decode(
- '' . \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) . '',
- \ENT_QUOTES,
- 'UTF-8'
- ),
-);
-
-$json->attachment['profile_url'] = array(
- 'type' => 'PropertyValue',
- 'name' => \__( 'Profile', 'activitypub' ),
- 'value' => \html_entity_decode(
- '' . \wp_parse_url( \get_author_posts_url( $author_id ), \PHP_URL_HOST ) . '',
- \ENT_QUOTES,
- 'UTF-8'
- ),
-);
-
-if ( \get_the_author_meta( 'user_url', $author_id ) ) {
- $json->attachment['user_url'] = array(
- 'type' => 'PropertyValue',
- 'name' => \__( 'Website', 'activitypub' ),
- 'value' => \html_entity_decode(
- '' . \wp_parse_url( \get_the_author_meta( 'user_url', $author_id ), \PHP_URL_HOST ) . '',
- \ENT_QUOTES,
- 'UTF-8'
- ),
- );
-}
-
-// filter output
-$json = \apply_filters( 'activitypub_json_author_array', $json, $author_id );
-
-// migrate to ActivityPub standard
-$json->attachment = array_values( $json->attachment );
+$user = \Activitypub\User_Factory::get_by_id( \get_the_author_meta( 'ID' ) );
/*
* Action triggerd prior to the ActivityPub profile being created and sent to the client
*/
-\do_action( 'activitypub_json_author_pre', $author_id );
+\do_action( 'activitypub_json_author_pre', $user->get_user_id() );
$options = 0;
// JSON_PRETTY_PRINT added in PHP 5.4
@@ -101,12 +19,12 @@ $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, $author_id );
+$options = \apply_filters( 'activitypub_json_author_options', $options, $user->get_user_id() );
\header( 'Content-Type: application/activity+json' );
-echo \wp_json_encode( $json, $options );
+echo \wp_json_encode( $user->to_array(), $options );
/*
* Action triggerd after the ActivityPub profile has been created and sent to the client
*/
-\do_action( 'activitypub_json_author_post', $author_id );
+\do_action( 'activitypub_json_author_post', $user->get_user_id() );
diff --git a/templates/followers-list.php b/templates/followers-list.php
index 7476192..733753e 100644
--- a/templates/followers-list.php
+++ b/templates/followers-list.php
@@ -1,3 +1,17 @@
+id === 'settings_page_activitypub' ) {
+ \load_template(
+ \dirname( __FILE__ ) . '/admin-header.php',
+ true,
+ array(
+ 'settings' => '',
+ 'welcome' => '',
+ 'followers' => 'active',
+ )
+ );
+}
+?>
+