From 1fd0cca18581cb55e2a6d56c842edc29a69129dc Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Thu, 10 Aug 2023 15:10:07 +0200
Subject: [PATCH 1/9] fix check!
---
includes/class-http.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/includes/class-http.php b/includes/class-http.php
index e243997..35003b8 100644
--- a/includes/class-http.php
+++ b/includes/class-http.php
@@ -52,7 +52,7 @@ class Http {
$response = \wp_safe_remote_post( $url, $args );
$code = \wp_remote_retrieve_response_code( $response );
- if ( 400 <= $code && 500 >= $code ) {
+ if ( $code <= 400 ) {
$response = new WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ) );
}
@@ -100,7 +100,7 @@ class Http {
$response = \wp_safe_remote_get( $url, $args );
$code = \wp_remote_retrieve_response_code( $response );
- if ( 400 <= $code && 500 >= $code ) {
+ if ( $code <= 400 ) {
$response = new WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ) );
}
From 6e2656311b61bf46630fc2b6946eedcc2fb5ac4a Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Thu, 10 Aug 2023 15:35:10 +0200
Subject: [PATCH 2/9] oops
---
includes/class-http.php | 4 ++--
includes/class-mention.php | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/includes/class-http.php b/includes/class-http.php
index 35003b8..5b741f4 100644
--- a/includes/class-http.php
+++ b/includes/class-http.php
@@ -52,7 +52,7 @@ class Http {
$response = \wp_safe_remote_post( $url, $args );
$code = \wp_remote_retrieve_response_code( $response );
- if ( $code <= 400 ) {
+ if ( $code >= 400 ) {
$response = new WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ) );
}
@@ -100,7 +100,7 @@ class Http {
$response = \wp_safe_remote_get( $url, $args );
$code = \wp_remote_retrieve_response_code( $response );
- if ( $code <= 400 ) {
+ if ( $code >= 400 ) {
$response = new WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ) );
}
diff --git a/includes/class-mention.php b/includes/class-mention.php
index fda0dd5..d6c4bbb 100644
--- a/includes/class-mention.php
+++ b/includes/class-mention.php
@@ -59,8 +59,7 @@ class Mention {
);
$the_content = \preg_replace_callback( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/', array( self::class, 'replace_with_links' ), $the_content );
-
- $the_content = str_replace( array_reverse( array_keys( $protected_tags ) ), array_reverse( array_values( $protected_tags ) ), $the_content );
+ $the_content = \str_replace( array_reverse( array_keys( $protected_tags ) ), array_reverse( array_values( $protected_tags ) ), $the_content );
return $the_content;
}
@@ -74,6 +73,7 @@ class Mention {
*/
public static function replace_with_links( $result ) {
$metadata = get_remote_metadata_by_actor( $result[0] );
+
if ( ! is_wp_error( $metadata ) && ! empty( $metadata['url'] ) ) {
$username = ltrim( $result[0], '@' );
if ( ! empty( $metadata['name'] ) ) {
From bc7e173fe05958530d2ea2709296ffe64ee0af4b Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Fri, 11 Aug 2023 09:22:46 +0200
Subject: [PATCH 3/9] also allow JSON
---
includes/functions.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/includes/functions.php b/includes/functions.php
index 6d9013d..73d3499 100644
--- a/includes/functions.php
+++ b/includes/functions.php
@@ -305,8 +305,9 @@ function is_activitypub_request() {
* and return true when the header includes at least one of the following:
* - application/activity+json
* - application/ld+json
+ * - application/json
*/
- if ( preg_match( '/(application\/(ld\+json|activity\+json))/', $accept ) ) {
+ if ( preg_match( '/(application\/(ld\+json|activity\+json|json))/i', $accept ) ) {
return true;
}
}
From 30eb07ba17f38de37f3d23e0a1dd7131e917573b Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Fri, 11 Aug 2023 09:23:49 +0200
Subject: [PATCH 4/9] add missing "type"
see https://git.joinfirefish.org/firefish/firefish/-/issues/10650#note_1011
---
includes/rest/class-collection.php | 29 +++++++++--------------------
1 file changed, 9 insertions(+), 20 deletions(-)
diff --git a/includes/rest/class-collection.php b/includes/rest/class-collection.php
index 3849594..3936d78 100644
--- a/includes/rest/class-collection.php
+++ b/includes/rest/class-collection.php
@@ -4,6 +4,7 @@ namespace Activitypub\Rest;
use WP_REST_Server;
use WP_REST_Response;
use Activitypub\Transformer\Post;
+use Activitypub\Activity\Activity;
use function Activitypub\esc_hashtag;
use function Activitypub\get_rest_url_by_path;
@@ -80,13 +81,9 @@ class Collection {
}
$response = array(
- '@context' => array(
- 'https://www.w3.org/ns/activitystreams',
- array(
- 'Hashtag' => 'as:Hashtag',
- ),
- ),
+ '@context' => Activity::CONTEXT,
'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $user_id ) ),
+ 'type' => 'Collection',
'totalItems' => count( $tags ),
'items' => array(),
);
@@ -124,23 +121,15 @@ class Collection {
$posts = \get_posts( $args );
$response = array(
- '@context' => 'https://www.w3.org/ns/activitystreams',
- array(
- 'ostatus' => 'http://ostatus.org#',
- 'atomUri' => 'ostatus:atomUri',
- 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri',
- 'conversation' => 'ostatus:conversation',
- 'sensitive' => 'as:sensitive',
- 'toot' => 'http://joinmastodon.org/ns#',
- 'votersCount' => 'toot:votersCount',
- ),
- 'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $user_id ) ),
- 'totalItems' => count( $posts ),
- 'items' => array(),
+ '@context' => Activity::CONTEXT,
+ 'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $user_id ) ),
+ 'type' => 'OrderedCollection',
+ 'totalItems' => count( $posts ),
+ 'orderedItems' => array(),
);
foreach ( $posts as $post ) {
- $response['items'][] = Post::transform( $post )->to_object()->to_array();
+ $response['orderedItems'][] = Post::transform( $post )->to_object()->to_array();
}
return new WP_REST_Response( $response, 200 );
From 626203002a3f58caf9ab576b895e4662b07b027c Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Fri, 11 Aug 2023 09:24:45 +0200
Subject: [PATCH 5/9] only include the minimum required fields for Accept call
---
includes/collection/class-followers.php | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/includes/collection/class-followers.php b/includes/collection/class-followers.php
index f28a17a..baadf09 100644
--- a/includes/collection/class-followers.php
+++ b/includes/collection/class-followers.php
@@ -265,11 +265,18 @@ class Followers {
return;
}
- if ( isset( $object['user_id'] ) ) {
- unset( $object['user_id'] );
- }
-
- unset( $object['@context'] );
+ // only send minimal data
+ $object = array_intersect_key(
+ $object,
+ array_flip(
+ array(
+ 'id',
+ 'type',
+ 'actor',
+ 'object',
+ )
+ )
+ );
$user = Users::get_by_id( $user_id );
@@ -282,7 +289,7 @@ class Followers {
$activity->set_object( $object );
$activity->set_actor( $user->get_id() );
$activity->set_to( $actor );
- $activity->set_id( $user->get_id() . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) );
+ $activity->set_id( $user->get_id() . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) . '-' . \time() );
$activity = $activity->to_json();
From 69ba1c87e127b4c654aaa9f9edb3c4f816672712 Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Fri, 11 Aug 2023 11:16:06 +0200
Subject: [PATCH 6/9] fix sticky posts endpoint
---
includes/rest/class-collection.php | 43 +++++++++++++++++++++++-------
1 file changed, 33 insertions(+), 10 deletions(-)
diff --git a/includes/rest/class-collection.php b/includes/rest/class-collection.php
index 3936d78..b29a090 100644
--- a/includes/rest/class-collection.php
+++ b/includes/rest/class-collection.php
@@ -5,8 +5,10 @@ use WP_REST_Server;
use WP_REST_Response;
use Activitypub\Transformer\Post;
use Activitypub\Activity\Activity;
+use Activitypub\Collection\Users as User_Collection;
use function Activitypub\esc_hashtag;
+use function Activitypub\is_single_user;
use function Activitypub\get_rest_url_by_path;
/**
@@ -65,7 +67,13 @@ class Collection {
*/
public static function tags_get( $request ) {
$user_id = $request->get_param( 'user_id' );
- $number = 4;
+ $user = User_Collection::get_by_various( $user_id );
+
+ if ( is_wp_error( $user ) ) {
+ return $user;
+ }
+
+ $number = 4;
$tags = \get_terms(
array(
@@ -82,7 +90,7 @@ class Collection {
$response = array(
'@context' => Activity::CONTEXT,
- 'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $user_id ) ),
+ 'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $user->get__id() ) ),
'type' => 'Collection',
'totalItems' => count( $tags ),
'items' => array(),
@@ -108,17 +116,32 @@ class Collection {
*/
public static function featured_get( $request ) {
$user_id = $request->get_param( 'user_id' );
+ $user = User_Collection::get_by_various( $user_id );
- $args = array(
- 'post__in' => \get_option( 'sticky_posts' ),
- 'ignore_sticky_posts' => 1,
- );
-
- if ( $user_id > 0 ) {
- $args['author'] = $user_id;
+ if ( is_wp_error( $user ) ) {
+ return $user;
}
- $posts = \get_posts( $args );
+ $sticky_posts = \get_option( 'sticky_posts' );
+
+ if ( ! is_single_user() && User_Collection::BLOG_USER_ID === $user->get__id() ) {
+ $posts = array();
+ } elseif ( $sticky_posts ) {
+ $args = array(
+ 'post__in' => $sticky_posts,
+ 'ignore_sticky_posts' => 1,
+ 'orderby' => 'date',
+ 'order' => 'DESC',
+ );
+
+ if ( $user->get__id() > 0 ) {
+ $args['author'] = $user->get__id();
+ }
+
+ $posts = \get_posts( $args );
+ } else {
+ $posts = array();
+ }
$response = array(
'@context' => Activity::CONTEXT,
From a9648798a8eb21d6ec2f9364917f145ba63c6e0b Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Fri, 11 Aug 2023 12:20:56 +0200
Subject: [PATCH 7/9] input fields should be readonly
---
templates/welcome.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/templates/welcome.php b/templates/welcome.php
index 891bb54..c193a6e 100644
--- a/templates/welcome.php
+++ b/templates/welcome.php
@@ -30,13 +30,13 @@
-
+
-
+
@@ -62,13 +62,13 @@
-
+
-
+
From 6f63e6c6517c300e9eebc7bd61e1c97914665b58 Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Fri, 11 Aug 2023 20:33:50 +0200
Subject: [PATCH 8/9] update readme
---
README.md | 6 ++++--
readme.txt | 6 ++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 55d2895..cdc6d62 100644
--- a/README.md
+++ b/README.md
@@ -75,11 +75,10 @@ Implemented:
* follow (accept follows)
* share posts
* receive comments/reactions
+* signature verification
To implement:
-* signature verification
-* better WordPress integration
* better configuration possibilities
* threaded comments support
@@ -120,6 +119,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu
* Add: Signature Verification: https://docs.joinmastodon.org/spec/security/ .
* Add: a Followers Block.
* Add: Simple caching
+* Add: Collection endpoints for Featured Tags and Featured Posts
* Update: Complete rewrite of the Follower-System based on Custom Post Types.
* Update: Improved linter (PHPCS)
* Compatibility: Add a new conditional, `\Activitypub\is_activitypub_request()`, to allow third-party plugins to detect ActivityPub requests.
@@ -127,6 +127,8 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu
* Compatibility: Indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.3.
* Compatibility: Avoid PHP notice on sites using PHP 8.2.
* Fixed: Load the plugin later in the WordPress code lifecycle to avoid errors in some requests.
+* Fixed: Updating posts
+* Fixed: Hashtag now support CamelCase and UTF-8
### 0.17.0 ###
diff --git a/readme.txt b/readme.txt
index a494cc5..a09b750 100644
--- a/readme.txt
+++ b/readme.txt
@@ -75,11 +75,10 @@ Implemented:
* follow (accept follows)
* share posts
* receive comments/reactions
+* signature verification
To implement:
-* signature verification
-* better WordPress integration
* better configuration possibilities
* threaded comments support
@@ -120,6 +119,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu
* Add: Signature Verification: https://docs.joinmastodon.org/spec/security/ .
* Add: a Followers Block.
* Add: Simple caching
+* Add: Collection endpoints for Featured Tags and Featured Posts
* Update: Complete rewrite of the Follower-System based on Custom Post Types.
* Update: Improved linter (PHPCS)
* Compatibility: Add a new conditional, `\Activitypub\is_activitypub_request()`, to allow third-party plugins to detect ActivityPub requests.
@@ -127,6 +127,8 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu
* Compatibility: Indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.3.
* Compatibility: Avoid PHP notice on sites using PHP 8.2.
* Fixed: Load the plugin later in the WordPress code lifecycle to avoid errors in some requests.
+* Fixed: Updating posts
+* Fixed: Hashtag now support CamelCase and UTF-8
= 0.17.0 =
From 14b91cf760a5f8b076fe7e34cd92a097ccbf7eb8 Mon Sep 17 00:00:00 2001
From: Matthias Pfefferle
Date: Sat, 12 Aug 2023 00:41:34 +0200
Subject: [PATCH 9/9] remote-follow endpoint (#392)
Adds an endpoint at `users/$user_id/follow-me` to return the follow template for a remote user, to enable following them more easily.
---
includes/class-webfinger.php | 108 +++++++++++++++++++++++++++++++++-
includes/rest/class-users.php | 53 +++++++++++++++++
2 files changed, 160 insertions(+), 1 deletion(-)
diff --git a/includes/class-webfinger.php b/includes/class-webfinger.php
index 9a53ac4..c077f12 100644
--- a/includes/class-webfinger.php
+++ b/includes/class-webfinger.php
@@ -62,7 +62,7 @@ class Webfinger {
$response = \wp_remote_get(
$url,
array(
- 'headers' => array( 'Accept' => 'application/activity+json' ),
+ 'headers' => array( 'Accept' => 'application/jrd+json' ),
'redirection' => 0,
'timeout' => 2,
)
@@ -94,4 +94,110 @@ class Webfinger {
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
return $link;
}
+
+ /**
+ * Convert a URI string to an identifier and its host.
+ * Automatically adds acct: if it's missing.
+ *
+ * @param string $url The URI (acct:, mailto:, http:, https:)
+ *
+ * @return WP_Error|array Error reaction or array with
+ * identifier and host as values
+ */
+ public static function get_identifier_and_host( $url ) {
+ // remove leading @
+ $url = ltrim( $url, '@' );
+
+ if ( ! preg_match( '/^([a-zA-Z+]+):/', $url, $match ) ) {
+ $identifier = 'acct:' . $url;
+ $scheme = 'acct';
+ } else {
+ $identifier = $url;
+ $scheme = $match[1];
+ }
+
+ $host = null;
+
+ switch ( $scheme ) {
+ case 'acct':
+ case 'mailto':
+ case 'xmpp':
+ if ( strpos( $identifier, '@' ) !== false ) {
+ $host = substr( $identifier, strpos( $identifier, '@' ) + 1 );
+ }
+ break;
+ default:
+ $host = wp_parse_url( $identifier, PHP_URL_HOST );
+ break;
+ }
+
+ if ( empty( $host ) ) {
+ return new WP_Error( 'invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) );
+ }
+
+ return array( $identifier, $host );
+ }
+
+ /**
+ * Get the WebFinger data for a given URI
+ *
+ * @param string $identifier The Identifier: @
+ * @param string $host The Host: @
+ *
+ * @return WP_Error|array Error reaction or array with
+ * identifier and host as values
+ */
+ public static function get_data( $identifier, $host ) {
+ $webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier );
+
+ $response = wp_safe_remote_get(
+ $webfinger_url,
+ array(
+ 'headers' => array( 'Accept' => 'application/jrd+json' ),
+ 'redirection' => 0,
+ 'timeout' => 2,
+ )
+ );
+
+ if ( is_wp_error( $response ) ) {
+ return new WP_Error( 'webfinger_url_not_accessible', null, $webfinger_url );
+ }
+
+ $body = wp_remote_retrieve_body( $response );
+
+ return json_decode( $body, true );
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return void
+ */
+ public static function get_remote_follow_endpoint( $uri ) {
+ $identifier_and_host = self::get_identifier_and_host( $uri );
+
+ if ( is_wp_error( $identifier_and_host ) ) {
+ return $identifier_and_host;
+ }
+
+ list( $identifier, $host ) = $identifier_and_host;
+
+ $data = self::get_data( $identifier, $host );
+
+ if ( is_wp_error( $data ) ) {
+ return $data;
+ }
+
+ if ( empty( $data['links'] ) ) {
+ return new WP_Error( 'webfinger_url_invalid_response', null, $data );
+ }
+
+ foreach ( $data['links'] as $link ) {
+ if ( 'http://ostatus.org/schema/1.0/subscribe' === $link['rel'] ) {
+ return $link['template'];
+ }
+ }
+
+ return new WP_Error( 'webfinger_remote_follow_endpoint_invalid', null, $data );
+ }
}
diff --git a/includes/rest/class-users.php b/includes/rest/class-users.php
index b678043..820f653 100644
--- a/includes/rest/class-users.php
+++ b/includes/rest/class-users.php
@@ -3,7 +3,9 @@ namespace Activitypub\Rest;
use WP_Error;
use WP_REST_Server;
+use WP_REST_Request;
use WP_REST_Response;
+use Activitypub\Webfinger;
use Activitypub\Activity\Activity;
use Activitypub\Collection\Users as User_Collection;
@@ -40,6 +42,25 @@ class Users {
),
)
);
+
+ \register_rest_route(
+ ACTIVITYPUB_REST_NAMESPACE,
+ '/users/(?P[\w\-\.]+)/remote-follow',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( self::class, 'remote_follow_get' ),
+
+ 'args' => array(
+ 'resource' => array(
+ 'required' => true,
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'permission_callback' => '__return_true',
+ ),
+ )
+ );
}
/**
@@ -80,6 +101,38 @@ class Users {
return $response;
}
+
+ /**
+ * Endpoint for remote follow UI/Block
+ *
+ * @param WP_REST_Request $request The request object.
+ *
+ * @return void|string The URL to the remote follow page
+ */
+ public static function remote_follow_get( WP_REST_Request $request ) {
+ $resource = $request->get_param( 'resource' );
+ $user_id = $request->get_param( 'user_id' );
+ $user = User_Collection::get_by_various( $user_id );
+
+ if ( is_wp_error( $user ) ) {
+ return $user;
+ }
+
+ $template = Webfinger::get_remote_follow_endpoint( $resource );
+
+ if ( is_wp_error( $template ) ) {
+ return $template;
+ }
+
+ $resource = $user->get_resource();
+ $url = str_replace( '{uri}', $resource, $template );
+
+ return new WP_REST_Response(
+ array( 'url' => $url ),
+ 200
+ );
+ }
+
/**
* The supported parameters
*