From c4daffe5c60c95d06c0b88aeba4d0537cb9813bb Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Wed, 18 Oct 2023 16:20:06 -0500 Subject: [PATCH 01/26] Shortcodes: only register when needed --- activitypub.php | 1 - includes/class-shortcodes.php | 20 +++++++++++++------- includes/transformer/class-post.php | 6 ++++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/activitypub.php b/activitypub.php index 521a379..f232c57 100644 --- a/activitypub.php +++ b/activitypub.php @@ -69,7 +69,6 @@ function plugin_init() { \add_action( 'init', array( __NAMESPACE__ . '\Collection\Followers', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Admin', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Hashtag', 'init' ) ); - \add_action( 'init', array( __NAMESPACE__ . '\Shortcodes', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) ); diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 43be17b..708aa61 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -5,14 +5,9 @@ use function Activitypub\esc_hashtag; class Shortcodes { /** - * Class constructor, registering WordPress then Shortcodes + * Register the shortcodes */ - public static function init() { - // do not load on admin pages - if ( is_admin() ) { - return; - } - + public static function register() { foreach ( get_class_methods( self::class ) as $shortcode ) { if ( 'init' !== $shortcode ) { add_shortcode( 'ap_' . $shortcode, array( self::class, $shortcode ) ); @@ -20,6 +15,17 @@ class Shortcodes { } } + /** + * Unregister the shortcodes + */ + public static function unregister() { + foreach ( get_class_methods( self::class ) as $shortcode ) { + if ( 'init' !== $shortcode ) { + remove_shortcode( 'ap_' . $shortcode ); + } + } + } + /** * Generates output for the 'ap_hashtags' shortcode * diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php index 6e2f0aa..9250afe 100644 --- a/includes/transformer/class-post.php +++ b/includes/transformer/class-post.php @@ -5,6 +5,7 @@ use WP_Post; use Activitypub\Collection\Users; use Activitypub\Model\Blog_User; use Activitypub\Activity\Base_Object; +use Activitypub\Shortcodes; use function Activitypub\esc_hashtag; use function Activitypub\is_single_user; @@ -466,6 +467,8 @@ class Post { $post = $this->wp_post; $content = $this->get_post_content_template(); + // Register our shortcodes just in time. + Shortcodes::register(); // Fill in the shortcodes. setup_postdata( $post ); $content = do_shortcode( $content ); @@ -477,6 +480,9 @@ class Post { $content = \apply_filters( 'activitypub_the_content', $content, $post ); + // Don't need these any more, should never appear in a post. + Shortcodes::unregister(); + return $content; } From ff58070a5e9cc25223b5504645aab3fa4789f476 Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Wed, 18 Oct 2023 16:21:20 -0500 Subject: [PATCH 02/26] Revert "Shortcodes: only register when needed" This reverts commit c4daffe5c60c95d06c0b88aeba4d0537cb9813bb. --- activitypub.php | 1 + includes/class-shortcodes.php | 20 +++++++------------- includes/transformer/class-post.php | 6 ------ 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/activitypub.php b/activitypub.php index f232c57..521a379 100644 --- a/activitypub.php +++ b/activitypub.php @@ -69,6 +69,7 @@ function plugin_init() { \add_action( 'init', array( __NAMESPACE__ . '\Collection\Followers', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Admin', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Hashtag', 'init' ) ); + \add_action( 'init', array( __NAMESPACE__ . '\Shortcodes', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) ); diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 708aa61..43be17b 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -5,9 +5,14 @@ use function Activitypub\esc_hashtag; class Shortcodes { /** - * Register the shortcodes + * Class constructor, registering WordPress then Shortcodes */ - public static function register() { + public static function init() { + // do not load on admin pages + if ( is_admin() ) { + return; + } + foreach ( get_class_methods( self::class ) as $shortcode ) { if ( 'init' !== $shortcode ) { add_shortcode( 'ap_' . $shortcode, array( self::class, $shortcode ) ); @@ -15,17 +20,6 @@ class Shortcodes { } } - /** - * Unregister the shortcodes - */ - public static function unregister() { - foreach ( get_class_methods( self::class ) as $shortcode ) { - if ( 'init' !== $shortcode ) { - remove_shortcode( 'ap_' . $shortcode ); - } - } - } - /** * Generates output for the 'ap_hashtags' shortcode * diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php index 9250afe..6e2f0aa 100644 --- a/includes/transformer/class-post.php +++ b/includes/transformer/class-post.php @@ -5,7 +5,6 @@ use WP_Post; use Activitypub\Collection\Users; use Activitypub\Model\Blog_User; use Activitypub\Activity\Base_Object; -use Activitypub\Shortcodes; use function Activitypub\esc_hashtag; use function Activitypub\is_single_user; @@ -467,8 +466,6 @@ class Post { $post = $this->wp_post; $content = $this->get_post_content_template(); - // Register our shortcodes just in time. - Shortcodes::register(); // Fill in the shortcodes. setup_postdata( $post ); $content = do_shortcode( $content ); @@ -480,9 +477,6 @@ class Post { $content = \apply_filters( 'activitypub_the_content', $content, $post ); - // Don't need these any more, should never appear in a post. - Shortcodes::unregister(); - return $content; } From 33b61ca2b97ad0e7e5afd8c647be81855b3332fa Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Thu, 19 Oct 2023 14:46:31 -0500 Subject: [PATCH 03/26] Shortcodes: only register when needed (#526) --- activitypub.php | 1 - includes/class-shortcodes.php | 20 +++++++++++++------- includes/transformer/class-post.php | 6 ++++++ tests/test-class-activitypub-shortcodes.php | 7 +++++++ 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/activitypub.php b/activitypub.php index 521a379..f232c57 100644 --- a/activitypub.php +++ b/activitypub.php @@ -69,7 +69,6 @@ function plugin_init() { \add_action( 'init', array( __NAMESPACE__ . '\Collection\Followers', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Admin', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Hashtag', 'init' ) ); - \add_action( 'init', array( __NAMESPACE__ . '\Shortcodes', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) ); \add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) ); diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 43be17b..708aa61 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -5,14 +5,9 @@ use function Activitypub\esc_hashtag; class Shortcodes { /** - * Class constructor, registering WordPress then Shortcodes + * Register the shortcodes */ - public static function init() { - // do not load on admin pages - if ( is_admin() ) { - return; - } - + public static function register() { foreach ( get_class_methods( self::class ) as $shortcode ) { if ( 'init' !== $shortcode ) { add_shortcode( 'ap_' . $shortcode, array( self::class, $shortcode ) ); @@ -20,6 +15,17 @@ class Shortcodes { } } + /** + * Unregister the shortcodes + */ + public static function unregister() { + foreach ( get_class_methods( self::class ) as $shortcode ) { + if ( 'init' !== $shortcode ) { + remove_shortcode( 'ap_' . $shortcode ); + } + } + } + /** * Generates output for the 'ap_hashtags' shortcode * diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php index 6e2f0aa..9250afe 100644 --- a/includes/transformer/class-post.php +++ b/includes/transformer/class-post.php @@ -5,6 +5,7 @@ use WP_Post; use Activitypub\Collection\Users; use Activitypub\Model\Blog_User; use Activitypub\Activity\Base_Object; +use Activitypub\Shortcodes; use function Activitypub\esc_hashtag; use function Activitypub\is_single_user; @@ -466,6 +467,8 @@ class Post { $post = $this->wp_post; $content = $this->get_post_content_template(); + // Register our shortcodes just in time. + Shortcodes::register(); // Fill in the shortcodes. setup_postdata( $post ); $content = do_shortcode( $content ); @@ -477,6 +480,9 @@ class Post { $content = \apply_filters( 'activitypub_the_content', $content, $post ); + // Don't need these any more, should never appear in a post. + Shortcodes::unregister(); + return $content; } diff --git a/tests/test-class-activitypub-shortcodes.php b/tests/test-class-activitypub-shortcodes.php index 8637a61..b1413e8 100644 --- a/tests/test-class-activitypub-shortcodes.php +++ b/tests/test-class-activitypub-shortcodes.php @@ -1,6 +1,10 @@ assertEquals( '

hallo

', $content ); + Shortcodes::unregister(); } public function test_password_protected_content() { + Shortcodes::register(); global $post; $post_id = -98; // negative ID, to avoid clash with a valid post @@ -54,5 +60,6 @@ class Test_Activitypub_Shortcodes extends WP_UnitTestCase { wp_reset_postdata(); $this->assertEquals( '', $content ); + Shortcodes::unregister(); } } From a40bd8408a9115307a540741e41f65052ab962f7 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Sat, 21 Oct 2023 11:23:05 +0200 Subject: [PATCH 04/26] Various improvements (#527) * remove unused code * check if `$data['object']` is a sting * do not index application user * this fixes GoToSocial errors * do not cache errors * re-added the fragment See https://github.com/superseriousbusiness/gotosocial/issues/2280 * Fix coding standards * do not verify signature on head request --- includes/collection/class-followers.php | 10 ++-------- includes/functions.php | 13 +++---------- includes/model/class-application-user.php | 4 ++++ includes/rest/class-inbox.php | 2 +- includes/rest/class-server.php | 4 ++++ 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/includes/collection/class-followers.php b/includes/collection/class-followers.php index a9fe298..c2ad01f 100644 --- a/includes/collection/class-followers.php +++ b/includes/collection/class-followers.php @@ -173,8 +173,6 @@ class Followers { return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ), array( 'status' => 400 ) ); } - $error = null; - $follower = new Follower(); $follower->from_array( $meta ); @@ -184,14 +182,10 @@ class Followers { return $id; } - $meta = get_post_meta( $id, 'activitypub_user_id' ); - - if ( $error ) { - self::add_error( $id, $error ); - } + $post_meta = get_post_meta( $id, 'activitypub_user_id' ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict - if ( is_array( $meta ) && ! in_array( $user_id, $meta ) ) { + if ( is_array( $post_meta ) && ! in_array( $user_id, $post_meta ) ) { add_post_meta( $id, 'activitypub_user_id', $user_id ); wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); } diff --git a/includes/functions.php b/includes/functions.php index 883cd3f..99b433f 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -74,32 +74,25 @@ function get_remote_metadata_by_actor( $actor, $cached = true ) { if ( ! \wp_http_validate_url( $actor ) ) { $metadata = new WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), array( 'status' => 400, 'actor' => $actor ) ); - \set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period. return $metadata; } - $short_timeout = function() { - return 10; - }; - add_filter( 'activitypub_remote_get_timeout', $short_timeout ); $response = Http::get( $actor ); - remove_filter( 'activitypub_remote_get_timeout', $short_timeout ); + if ( \is_wp_error( $response ) ) { - \set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period. return $response; } $metadata = \wp_remote_retrieve_body( $response ); $metadata = \json_decode( $metadata, true ); - \set_transient( $transient_key, $metadata, WEEK_IN_SECONDS ); - if ( ! $metadata ) { $metadata = new WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), array( 'status' => 400, 'actor' => $actor ) ); - \set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period. return $metadata; } + \set_transient( $transient_key, $metadata, WEEK_IN_SECONDS ); + return $metadata; } diff --git a/includes/model/class-application-user.php b/includes/model/class-application-user.php index 1cfcec0..ae8b641 100644 --- a/includes/model/class-application-user.php +++ b/includes/model/class-application-user.php @@ -69,4 +69,8 @@ class Application_User extends Blog_User { public function get_moderators() { return null; } + + public function get_indexable() { + return false; + } } diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 5d65b9b..9c5961a 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -416,7 +416,7 @@ class Inbox { $recipient_items = array_merge( $recipient_items, $recipient ); } - if ( array_key_exists( $i, $data['object'] ) ) { + if ( is_array( $data['object'] ) && array_key_exists( $i, $data['object'] ) ) { if ( is_array( $data['object'][ $i ] ) ) { $recipient = $data['object'][ $i ]; } else { diff --git a/includes/rest/class-server.php b/includes/rest/class-server.php index e1a1037..19d35b2 100644 --- a/includes/rest/class-server.php +++ b/includes/rest/class-server.php @@ -74,6 +74,10 @@ class Server { * @return mixed|WP_Error The response, error, or modified response. */ public static function authorize_activitypub_requests( $response, $handler, $request ) { + if ( 'HEAD' === $request->get_method() ) { + return $response; + } + $route = $request->get_route(); // check if it is an activitypub request and exclude webfinger and nodeinfo endpoints From 247899312acf5e88d920062eb3ee0c526c5966dc Mon Sep 17 00:00:00 2001 From: Chaitanya110703 <116812461+Chaitanya110703@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:56:17 +0530 Subject: [PATCH 05/26] doc(README): remove typo (#528) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93f3e85..f1c45e0 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Use shortcodes instead of custom templates, to setup the Activity Post-Content ([#250](https://github.com/pfefferle/wordpress-activitypub/pull/250)) props [@toolstack](https://github.com/toolstack) * Remove custom REST Server, because the needed changes are now merged into Core. * Fix hashtags ([#261](https://github.com/pfefferle/wordpress-activitypub/pull/261)) props [@akirk](https://github.com/akirk) -* Change priorites, to maybe fix the hashtag issue +* Change priorities, to maybe fix the hashtag issue ### 0.15.0 ### From 0ab8df539e6b300efd2956ab0bc8e6b28ec62b63 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Oct 2023 08:28:25 +0200 Subject: [PATCH 06/26] simplify check --- includes/rest/class-server.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/rest/class-server.php b/includes/rest/class-server.php index 19d35b2..a8706ea 100644 --- a/includes/rest/class-server.php +++ b/includes/rest/class-server.php @@ -90,12 +90,12 @@ class Server { } // POST-Requets are always signed - if ( 'get' !== \strtolower( $request->get_method() ) ) { + if ( 'GET' !== $request->get_method() ) { $verified_request = Signature::verify_http_signature( $request ); if ( \is_wp_error( $verified_request ) ) { return $verified_request; } - } elseif ( 'get' === \strtolower( $request->get_method() ) ) { // GET-Requests are only signed in secure mode + } elseif ( 'GET' === $request->get_method() ) { // GET-Requests are only signed in secure mode if ( ACTIVITYPUB_AUTHORIZED_FETCH ) { $verified_request = Signature::verify_http_signature( $request ); if ( \is_wp_error( $verified_request ) ) { From acc632f05c04279ca45090a17f8b58ea4dba8a4f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Oct 2023 09:03:15 +0200 Subject: [PATCH 07/26] prepare v1.0.8 --- README.md | 13 +++++++++++-- activitypub.php | 2 +- readme.txt | 11 ++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f1c45e0..f320c00 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Tags:** OStatus, fediverse, activitypub, activitystream **Requires at least:** 4.7 **Tested up to:** 6.3 -**Stable tag:** 1.0.7 +**Stable tag:** 1.0.8 **Requires PHP:** 5.6 **License:** MIT **License URI:** http://opensource.org/licenses/MIT @@ -105,6 +105,15 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). +### 1.0.8 ### + +* Fixed: blocking of HEAD requests +* Fixed: PHP fatal error +* Fixed: several typos +* Improved: loading of shortcodes +* Updated: caching of followers +* Updated: Application-User is no longer "indexable" + ### 1.0.7 ### * Fixed: broken function call @@ -210,7 +219,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Use shortcodes instead of custom templates, to setup the Activity Post-Content ([#250](https://github.com/pfefferle/wordpress-activitypub/pull/250)) props [@toolstack](https://github.com/toolstack) * Remove custom REST Server, because the needed changes are now merged into Core. * Fix hashtags ([#261](https://github.com/pfefferle/wordpress-activitypub/pull/261)) props [@akirk](https://github.com/akirk) -* Change priorities, to maybe fix the hashtag issue +* Change priorites, to maybe fix the hashtag issue ### 0.15.0 ### diff --git a/activitypub.php b/activitypub.php index f232c57..62556b4 100644 --- a/activitypub.php +++ b/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: 1.0.7 + * Version: 1.0.8 * Author: Matthias Pfefferle & Automattic * Author URI: https://automattic.com/ * License: MIT diff --git a/readme.txt b/readme.txt index 03e9b39..5cc8a6f 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: automattic, pfefferle, mediaformat, mattwiebe, akirk, jeherve, nur Tags: OStatus, fediverse, activitypub, activitystream Requires at least: 4.7 Tested up to: 6.3 -Stable tag: 1.0.7 +Stable tag: 1.0.8 Requires PHP: 5.6 License: MIT License URI: http://opensource.org/licenses/MIT @@ -105,6 +105,15 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). += 1.0.8 = + +* Fixed: blocking of HEAD requests +* Fixed: PHP fatal error +* Fixed: several typos +* Improved: loading of shortcodes +* Updated: caching of followers +* Updated: Application-User is no longer "indexable" + = 1.0.7 = * Fixed: broken function call From b55c5d1666df6ad38ebb1e99da1643c5ce1325bb Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Oct 2023 14:54:40 +0200 Subject: [PATCH 08/26] use 401 instead of 403 --- includes/class-signature.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/includes/class-signature.php b/includes/class-signature.php index 1789dd6..0b102b3 100644 --- a/includes/class-signature.php +++ b/includes/class-signature.php @@ -259,7 +259,7 @@ class Signature { } if ( ! isset( $headers['signature'] ) ) { - return new WP_Error( 'activitypub_signature', __( 'Request not signed', 'activitypub' ), array( 'status' => 403 ) ); + return new WP_Error( 'activitypub_signature', __( 'Request not signed', 'activitypub' ), array( 'status' => 401 ) ); } if ( array_key_exists( 'signature', $headers ) ) { @@ -269,7 +269,7 @@ class Signature { } if ( ! isset( $signature_block ) || ! $signature_block ) { - return new WP_Error( 'activitypub_signature', __( 'Incompatible request signature. keyId and signature are required', 'activitypub' ), array( 'status' => 403 ) ); + return new WP_Error( 'activitypub_signature', __( 'Incompatible request signature. keyId and signature are required', 'activitypub' ), array( 'status' => 401 ) ); } $signed_headers = $signature_block['headers']; @@ -279,12 +279,12 @@ class Signature { $signed_data = self::get_signed_data( $signed_headers, $signature_block, $headers ); if ( ! $signed_data ) { - return new WP_Error( 'activitypub_signature', __( 'Signed request date outside acceptable time window', 'activitypub' ), array( 'status' => 403 ) ); + return new WP_Error( 'activitypub_signature', __( 'Signed request date outside acceptable time window', 'activitypub' ), array( 'status' => 401 ) ); } $algorithm = self::get_signature_algorithm( $signature_block ); if ( ! $algorithm ) { - return new WP_Error( 'activitypub_signature', __( 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)', 'activitypub' ), array( 'status' => 403 ) ); + return new WP_Error( 'activitypub_signature', __( 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)', 'activitypub' ), array( 'status' => 401 ) ); } if ( \in_array( 'digest', $signed_headers, true ) && isset( $body ) ) { @@ -300,7 +300,7 @@ class Signature { } if ( \base64_encode( \hash( $hashalg, $body, true ) ) !== $digest[1] ) { // phpcs:ignore - return new WP_Error( 'activitypub_signature', __( 'Invalid Digest header', 'activitypub' ), array( 'status' => 403 ) ); + return new WP_Error( 'activitypub_signature', __( 'Invalid Digest header', 'activitypub' ), array( 'status' => 401 ) ); } } @@ -313,7 +313,7 @@ class Signature { $verified = \openssl_verify( $signed_data, $signature_block['signature'], $public_key, $algorithm ) > 0; if ( ! $verified ) { - return new WP_Error( 'activitypub_signature', __( 'Invalid signature', 'activitypub' ), array( 'status' => 403 ) ); + return new WP_Error( 'activitypub_signature', __( 'Invalid signature', 'activitypub' ), array( 'status' => 401 ) ); } return $verified; } @@ -333,7 +333,7 @@ class Signature { if ( isset( $actor['publicKey']['publicKeyPem'] ) ) { return \rtrim( $actor['publicKey']['publicKeyPem'] ); // phpcs:ignore } - return new WP_Error( 'activitypub_no_remote_key_found', __( 'No Public-Key found', 'activitypub' ), array( 'status' => 403 ) ); + return new WP_Error( 'activitypub_no_remote_key_found', __( 'No Public-Key found', 'activitypub' ), array( 'status' => 401 ) ); } /** From 9ac5d84f5c72dca6b1f32fe6497497ecc5960e4e Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Oct 2023 14:56:37 +0200 Subject: [PATCH 09/26] updated readme --- README.md | 1 + readme.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index f320c00..1f0c6f2 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Fixed: blocking of HEAD requests * Fixed: PHP fatal error * Fixed: several typos +* Fixed: error codes * Improved: loading of shortcodes * Updated: caching of followers * Updated: Application-User is no longer "indexable" diff --git a/readme.txt b/readme.txt index 5cc8a6f..9b0b1b9 100644 --- a/readme.txt +++ b/readme.txt @@ -110,6 +110,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Fixed: blocking of HEAD requests * Fixed: PHP fatal error * Fixed: several typos +* Fixed: error codes * Improved: loading of shortcodes * Updated: caching of followers * Updated: Application-User is no longer "indexable" From b946ef3de1a211dc792bed6808091b78086f83a0 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Oct 2023 14:57:58 +0200 Subject: [PATCH 10/26] more consistent use of response content type (#529) * more consistent use of response content type * update readme * fix typo --- README.md | 1 + includes/rest/class-collection.php | 15 ++++++++++++--- includes/rest/class-followers.php | 6 +++--- includes/rest/class-following.php | 6 +++--- includes/rest/class-inbox.php | 17 +++++++++++------ includes/rest/class-outbox.php | 7 +++---- includes/rest/class-server.php | 7 +++---- includes/rest/class-users.php | 6 +++--- readme.txt | 1 + 9 files changed, 40 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 1f0c6f2..18b5742 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Improved: loading of shortcodes * Updated: caching of followers * Updated: Application-User is no longer "indexable" +* Updated: more consistent usage of the `application/activity+json` Content-Type ### 1.0.7 ### diff --git a/includes/rest/class-collection.php b/includes/rest/class-collection.php index 5fa585d..2e6522e 100644 --- a/includes/rest/class-collection.php +++ b/includes/rest/class-collection.php @@ -117,7 +117,10 @@ class Collection { ); } - return new WP_REST_Response( $response, 200 ); + $rest_response = new WP_REST_Response( $response, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); + + return $rest_response; } /** @@ -168,7 +171,10 @@ class Collection { $response['orderedItems'][] = Post::transform( $post )->to_object()->to_array(); } - return new WP_REST_Response( $response, 200 ); + $rest_response = new WP_REST_Response( $response, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); + + return $rest_response; } /** @@ -192,7 +198,10 @@ class Collection { $response['orderedItems'][] = $user->get_url(); } - return new WP_REST_Response( $response, 200 ); + $rest_response = new WP_REST_Response( $response, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); + + return $rest_response; } /** diff --git a/includes/rest/class-followers.php b/includes/rest/class-followers.php index b7be9d0..71e4840 100644 --- a/includes/rest/class-followers.php +++ b/includes/rest/class-followers.php @@ -103,10 +103,10 @@ class Followers { $data['followers'] ); - $response = new WP_REST_Response( $json, 200 ); - $response->header( 'Content-Type', 'application/activity+json' ); + $rest_response = new WP_REST_Response( $json, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); - return $response; + return $rest_response; } /** diff --git a/includes/rest/class-following.php b/includes/rest/class-following.php index 33bdf65..b591ab6 100644 --- a/includes/rest/class-following.php +++ b/includes/rest/class-following.php @@ -79,10 +79,10 @@ class Following { $json->first = $json->partOf; // phpcs:ignore - $response = new \WP_REST_Response( $json, 200 ); - $response->header( 'Content-Type', 'application/activity+json' ); + $rest_response = new WP_REST_Response( $json, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); - return $response; + return $rest_response; } /** diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 9c5961a..747290e 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -109,11 +109,10 @@ class Inbox { */ \do_action( 'activitypub_inbox_post' ); - $response = new WP_REST_Response( $json, 200 ); + $rest_response = new WP_REST_Response( $json, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); - $response->header( 'Content-Type', 'application/activity+json' ); - - return $response; + return $rest_response; } /** @@ -138,7 +137,10 @@ class Inbox { \do_action( 'activitypub_inbox', $data, $user->get__id(), $type ); \do_action( "activitypub_inbox_{$type}", $data, $user->get__id() ); - return new WP_REST_Response( array(), 202 ); + $rest_response = new WP_REST_Response( array(), 202 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); + + return $rest_response; } /** @@ -183,7 +185,10 @@ class Inbox { \do_action( "activitypub_inbox_{$type}", $data, $user->ID ); } - return new WP_REST_Response( array(), 202 ); + $rest_response = new WP_REST_Response( array(), 202 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); + + return $rest_response; } /** diff --git a/includes/rest/class-outbox.php b/includes/rest/class-outbox.php index eb35e86..d640d17 100644 --- a/includes/rest/class-outbox.php +++ b/includes/rest/class-outbox.php @@ -123,11 +123,10 @@ class Outbox { */ \do_action( 'activitypub_outbox_post' ); - $response = new WP_REST_Response( $json, 200 ); + $rest_response = new WP_REST_Response( $json, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); - $response->header( 'Content-Type', 'application/activity+json' ); - - return $response; + return $rest_response; } /** diff --git a/includes/rest/class-server.php b/includes/rest/class-server.php index a8706ea..8239168 100644 --- a/includes/rest/class-server.php +++ b/includes/rest/class-server.php @@ -54,11 +54,10 @@ class Server { $json = $user->to_array(); - $response = new WP_REST_Response( $json, 200 ); + $rest_response = new WP_REST_Response( $json, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); - $response->header( 'Content-Type', 'application/activity+json' ); - - return $response; + return $rest_response; } /** diff --git a/includes/rest/class-users.php b/includes/rest/class-users.php index 31a92dd..9fb10ba 100644 --- a/includes/rest/class-users.php +++ b/includes/rest/class-users.php @@ -95,10 +95,10 @@ class Users { $json = $user->to_array(); - $response = new WP_REST_Response( $json, 200 ); - $response->header( 'Content-Type', 'application/activity+json' ); + $rest_response = new WP_REST_Response( $json, 200 ); + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) ); - return $response; + return $rest_response; } diff --git a/readme.txt b/readme.txt index 9b0b1b9..848a213 100644 --- a/readme.txt +++ b/readme.txt @@ -114,6 +114,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Improved: loading of shortcodes * Updated: caching of followers * Updated: Application-User is no longer "indexable" +* Updated: more consistent usage of the `application/activity+json` Content-Type = 1.0.7 = From 4d7c0594cdadf294e56e739c22423b356192d61e Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Oct 2023 16:16:24 +0200 Subject: [PATCH 11/26] remove featured tags endpoint --- includes/model/class-application-user.php | 4 ---- includes/model/class-user.php | 18 ------------------ 2 files changed, 22 deletions(-) diff --git a/includes/model/class-application-user.php b/includes/model/class-application-user.php index ae8b641..cf4d9cc 100644 --- a/includes/model/class-application-user.php +++ b/includes/model/class-application-user.php @@ -58,10 +58,6 @@ class Application_User extends Blog_User { return null; } - public function get_featured_tags() { - return null; - } - public function get_featured() { return null; } diff --git a/includes/model/class-user.php b/includes/model/class-user.php index f62772d..95c83d7 100644 --- a/includes/model/class-user.php +++ b/includes/model/class-user.php @@ -18,15 +18,6 @@ class User extends Actor { */ protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore - /** - * The Featured-Tags. - * - * @see https://docs.joinmastodon.org/spec/activitypub/#featuredTags - * - * @var string - */ - protected $featured_tags; - /** * The Featured-Posts. * @@ -235,15 +226,6 @@ class User extends Actor { return get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $this->get__id() ) ); } - /** - * Returns the Featured-Tags-API-Endpoint. - * - * @return string The Featured-Tags-Endpoint. - */ - public function get_featured_tags() { - return get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $this->get__id() ) ); - } - /** * Extend the User-Output with Attachments. * From 2664ae807ca149122e4b4c270d817f5061fad8d4 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 23 Oct 2023 16:18:28 +0200 Subject: [PATCH 12/26] update readme --- README.md | 1 + readme.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 18b5742..40c5398 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Updated: caching of followers * Updated: Application-User is no longer "indexable" * Updated: more consistent usage of the `application/activity+json` Content-Type +* Removed: featured tags endpoint ### 1.0.7 ### diff --git a/readme.txt b/readme.txt index 848a213..b04a987 100644 --- a/readme.txt +++ b/readme.txt @@ -115,6 +115,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Updated: caching of followers * Updated: Application-User is no longer "indexable" * Updated: more consistent usage of the `application/activity+json` Content-Type +* Removed: featured tags endpoint = 1.0.7 = From e91334e4d7c0d32bdaa49f965c9c3eb3a1e03435 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 24 Oct 2023 12:45:46 +0200 Subject: [PATCH 13/26] fix following endpoint (#531) * fix following endpoint * version bump --- README.md | 6 +++++- activitypub.php | 2 +- includes/rest/class-following.php | 1 + readme.txt | 6 +++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 40c5398..8156292 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Tags:** OStatus, fediverse, activitypub, activitystream **Requires at least:** 4.7 **Tested up to:** 6.3 -**Stable tag:** 1.0.8 +**Stable tag:** 1.0.9 **Requires PHP:** 5.6 **License:** MIT **License URI:** http://opensource.org/licenses/MIT @@ -105,6 +105,10 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). +### 1.0.9 ### + +* Fixed: broken following endpoint + ### 1.0.8 ### * Fixed: blocking of HEAD requests diff --git a/activitypub.php b/activitypub.php index 62556b4..91786e5 100644 --- a/activitypub.php +++ b/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: 1.0.8 + * Version: 1.0.9 * Author: Matthias Pfefferle & Automattic * Author URI: https://automattic.com/ * License: MIT diff --git a/includes/rest/class-following.php b/includes/rest/class-following.php index b591ab6..22c9d46 100644 --- a/includes/rest/class-following.php +++ b/includes/rest/class-following.php @@ -1,6 +1,7 @@ Date: Tue, 24 Oct 2023 13:00:22 +0200 Subject: [PATCH 14/26] improve error messages and codes (#532) * improve error messages and codes * version bump --- README.md | 6 +++++- activitypub.php | 2 +- includes/class-signature.php | 2 +- readme.txt | 6 +++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8156292..dcc91f2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Tags:** OStatus, fediverse, activitypub, activitystream **Requires at least:** 4.7 **Tested up to:** 6.3 -**Stable tag:** 1.0.9 +**Stable tag:** 1.0.10 **Requires PHP:** 5.6 **License:** MIT **License URI:** http://opensource.org/licenses/MIT @@ -105,6 +105,10 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). +### 1.0.10 ### + +* Improved: better error messages if remote profile is not accessible + ### 1.0.9 ### * Fixed: broken following endpoint diff --git a/activitypub.php b/activitypub.php index 91786e5..97f6067 100644 --- a/activitypub.php +++ b/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: 1.0.9 + * Version: 1.0.10 * Author: Matthias Pfefferle & Automattic * Author URI: https://automattic.com/ * License: MIT diff --git a/includes/class-signature.php b/includes/class-signature.php index 0b102b3..e66aa4b 100644 --- a/includes/class-signature.php +++ b/includes/class-signature.php @@ -328,7 +328,7 @@ class Signature { public static function get_remote_key( $key_id ) { // phpcs:ignore $actor = get_remote_metadata_by_actor( strip_fragment_from_url( $key_id ) ); // phpcs:ignore if ( \is_wp_error( $actor ) ) { - return $actor; + return new WP_Error( 'activitypub_no_remote_profile_found', __( 'No Profile found or Profile not accessible', 'activitypub' ), array( 'status' => 401 ) ); } if ( isset( $actor['publicKey']['publicKeyPem'] ) ) { return \rtrim( $actor['publicKey']['publicKeyPem'] ); // phpcs:ignore diff --git a/readme.txt b/readme.txt index bb562c6..7e48fc1 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: automattic, pfefferle, mediaformat, mattwiebe, akirk, jeherve, nur Tags: OStatus, fediverse, activitypub, activitystream Requires at least: 4.7 Tested up to: 6.3 -Stable tag: 1.0.9 +Stable tag: 1.0.10 Requires PHP: 5.6 License: MIT License URI: http://opensource.org/licenses/MIT @@ -105,6 +105,10 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). += 1.0.10 = + +* Improved: better error messages if remote profile is not accessible + = 1.0.9 = * Fixed: broken following endpoint From 8078512b8cd1d7ba1d3a7565b3fd9a9e04baac21 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 24 Oct 2023 14:54:03 +0200 Subject: [PATCH 15/26] small improvements --- includes/class-signature.php | 17 +++++++++++++---- includes/functions.php | 2 +- includes/rest/class-inbox.php | 2 +- includes/rest/class-server.php | 5 +++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/includes/class-signature.php b/includes/class-signature.php index e66aa4b..d021cf0 100644 --- a/includes/class-signature.php +++ b/includes/class-signature.php @@ -4,6 +4,7 @@ namespace Activitypub; use WP_Error; use DateTime; use DateTimeZone; +use WP_REST_Request; use Activitypub\Collection\Users; /** @@ -226,7 +227,7 @@ class Signature { /** * Verifies the http signatures * - * @param WP_REQUEST|array $request The request object or $_SERVER array. + * @param WP_REST_Request|array $request The request object or $_SERVER array. * * @return mixed A boolean or WP_Error. */ @@ -323,17 +324,25 @@ class Signature { * * @param string $key_id The URL to the public key. * - * @return WP_Error|string The public key. + * @return WP_Error|string The public key or WP_Error. */ public static function get_remote_key( $key_id ) { // phpcs:ignore $actor = get_remote_metadata_by_actor( strip_fragment_from_url( $key_id ) ); // phpcs:ignore if ( \is_wp_error( $actor ) ) { - return new WP_Error( 'activitypub_no_remote_profile_found', __( 'No Profile found or Profile not accessible', 'activitypub' ), array( 'status' => 401 ) ); + return new WP_Error( + 'activitypub_no_remote_profile_found', + __( 'No Profile found or Profile not accessible', 'activitypub' ), + array( 'status' => 401 ) + ); } if ( isset( $actor['publicKey']['publicKeyPem'] ) ) { return \rtrim( $actor['publicKey']['publicKeyPem'] ); // phpcs:ignore } - return new WP_Error( 'activitypub_no_remote_key_found', __( 'No Public-Key found', 'activitypub' ), array( 'status' => 401 ) ); + return new WP_Error( + 'activitypub_no_remote_key_found', + __( 'No Public-Key found', 'activitypub' ), + array( 'status' => 401 ) + ); } /** diff --git a/includes/functions.php b/includes/functions.php index 99b433f..b2972c0 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -42,7 +42,7 @@ function get_webfinger_resource( $user_id ) { * @param string $actor The Actor URL. * @param bool $cached If the result should be cached. * - * @return array The Actor profile as array + * @return array|WP_Error The Actor profile as array or WP_Error on failure. */ function get_remote_metadata_by_actor( $actor, $cached = true ) { $pre = apply_filters( 'pre_get_remote_metadata_by_actor', false, $actor ); diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index 747290e..9088993 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -160,7 +160,7 @@ class Inbox { 'rest_invalid_param', \__( 'No recipients found', 'activitypub' ), array( - 'status' => 404, + 'status' => 400, 'params' => array( 'to' => \__( 'Please check/validate "to" field', 'activitypub' ), 'bto' => \__( 'Please check/validate "bto" field', 'activitypub' ), diff --git a/includes/rest/class-server.php b/includes/rest/class-server.php index 8239168..0e6e4cc 100644 --- a/includes/rest/class-server.php +++ b/includes/rest/class-server.php @@ -2,6 +2,7 @@ namespace Activitypub\Rest; use stdClass; +use WP_Error; use WP_REST_Response; use Activitypub\Signature; use Activitypub\Model\Application_User; @@ -92,13 +93,13 @@ class Server { if ( 'GET' !== $request->get_method() ) { $verified_request = Signature::verify_http_signature( $request ); if ( \is_wp_error( $verified_request ) ) { - return $verified_request; + return new WP_Error( 'activitypub_signature_verification', $verified_request->get_error_message(), array( 'status' => 401 ) ); } } elseif ( 'GET' === $request->get_method() ) { // GET-Requests are only signed in secure mode if ( ACTIVITYPUB_AUTHORIZED_FETCH ) { $verified_request = Signature::verify_http_signature( $request ); if ( \is_wp_error( $verified_request ) ) { - return $verified_request; + return new WP_Error( 'activitypub_signature_verification', $verified_request->get_error_message(), array( 'status' => 401 ) ); } } } From 53adfe6b80b372ae907e85566907cabd4b3fe478 Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Wed, 25 Oct 2023 01:44:04 -0500 Subject: [PATCH 16/26] PHP 8.1 compatibility (#533) * PHP 8.1 compatibility * Update compat.php --------- Co-authored-by: Matthias Pfefferle --- includes/compat.php | 12 ++++++++++++ includes/rest/class-collection.php | 4 ++-- includes/rest/class-following.php | 2 +- includes/rest/class-nodeinfo.php | 4 ++-- includes/transformer/class-post.php | 2 ++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/includes/compat.php b/includes/compat.php index 4bee640..3dd405c 100644 --- a/includes/compat.php +++ b/includes/compat.php @@ -35,3 +35,15 @@ if ( ! function_exists( 'get_self_link' ) ) { return esc_url( apply_filters( 'self_link', set_url_scheme( 'http://' . $host['host'] . $path ) ) ); } } + +if ( ! function_exists( 'is_countable' ) ) { + /** + * Polyfill for `is_countable()` function added in PHP 7.3. + * + * @param mixed $value The value to check. + * @return bool True if `$value` is countable, otherwise false. + */ + function is_countable( $value ) { + return is_array( $value ) || $value instanceof \Countable; + } +} diff --git a/includes/rest/class-collection.php b/includes/rest/class-collection.php index 2e6522e..365641c 100644 --- a/includes/rest/class-collection.php +++ b/includes/rest/class-collection.php @@ -105,7 +105,7 @@ class Collection { '@context' => Activity::CONTEXT, 'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $user->get__id() ) ), 'type' => 'Collection', - 'totalItems' => count( $tags ), + 'totalItems' => is_countable( $tags ) ? count( $tags ) : 0, 'items' => array(), ); @@ -163,7 +163,7 @@ class Collection { '@context' => Activity::CONTEXT, 'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $user_id ) ), 'type' => 'OrderedCollection', - 'totalItems' => count( $posts ), + 'totalItems' => is_countable( $posts ) ? count( $posts ) : 0, 'orderedItems' => array(), ); diff --git a/includes/rest/class-following.php b/includes/rest/class-following.php index 22c9d46..58e4375 100644 --- a/includes/rest/class-following.php +++ b/includes/rest/class-following.php @@ -75,7 +75,7 @@ class Following { $items = apply_filters( 'activitypub_rest_following', array(), $user ); // phpcs:ignore - $json->totalItems = count( $items ); // phpcs:ignore + $json->totalItems = is_countable( $items ) ? count( $items ) : 0; // phpcs:ignore $json->orderedItems = $items; // phpcs:ignore $json->first = $json->partOf; // phpcs:ignore diff --git a/includes/rest/class-nodeinfo.php b/includes/rest/class-nodeinfo.php index 1f6277a..4829e75 100644 --- a/includes/rest/class-nodeinfo.php +++ b/includes/rest/class-nodeinfo.php @@ -88,7 +88,7 @@ class Nodeinfo { ) ); - if ( is_array( $users ) ) { + if ( is_countable( $users ) ) { $users = count( $users ); } else { $users = 1; @@ -145,7 +145,7 @@ class Nodeinfo { ) ); - if ( is_array( $users ) ) { + if ( is_countable( $users ) ) { $users = count( $users ); } else { $users = 1; diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php index 9250afe..36d357e 100644 --- a/includes/transformer/class-post.php +++ b/includes/transformer/class-post.php @@ -335,6 +335,8 @@ class Post { return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) ); } + // Default to Article. + $object_type = 'Article'; $post_type = \get_post_type( $this->wp_post ); switch ( $post_type ) { case 'post': From 9ff4d1251a7f2212d422a49835254465a115cb46 Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Fri, 27 Oct 2023 15:55:44 -0500 Subject: [PATCH 17/26] =?UTF-8?q?Attachments:=20add=20support=20for=20audi?= =?UTF-8?q?o=20=F0=9F=94=88=20and=20video=20=F0=9F=93=BC=20(#536)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * only in the block editor * update settings page copy: media, not just images --------- Co-authored-by: Matthias Pfefferle --- includes/transformer/class-post.php | 301 +++++++++++++++++----------- readme.txt | 4 + templates/settings.php | 11 +- 3 files changed, 195 insertions(+), 121 deletions(-) diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php index 36d357e..c10f0cf 100644 --- a/includes/transformer/class-post.php +++ b/includes/transformer/class-post.php @@ -143,78 +143,65 @@ class Post { } /** - * Returns the Image Attachments for this Post, parsed from blocks. - * @param int $max_images The maximum number of images to return. - * @param array $image_ids The image IDs to append new IDs to. + * Generates all Media Attachments for a Post. * - * @return array The image IDs. - */ - protected function get_block_image_ids( $max_images, $image_ids = [] ) { - $blocks = \parse_blocks( $this->wp_post->post_content ); - return self::get_image_ids_from_blocks( $blocks, $image_ids, $max_images ); - } - - /** - * Recursively get image IDs from blocks. - * @param array $blocks The blocks to search for image IDs - * @param array $image_ids The image IDs to append new IDs to - * @param int $max_images The maximum number of images to return. - * - * @return array The image IDs. - */ - protected static function get_image_ids_from_blocks( $blocks, $image_ids, $max_images ) { - foreach ( $blocks as $block ) { - // recurse into inner blocks - if ( ! empty( $block['innerBlocks'] ) ) { - $image_ids = self::get_image_ids_from_blocks( $block['innerBlocks'], $image_ids, $max_images ); - } - - switch ( $block['blockName'] ) { - case 'core/image': - case 'core/cover': - if ( ! empty( $block['attrs']['id'] ) ) { - $image_ids[] = $block['attrs']['id']; - } - break; - case 'jetpack/slideshow': - case 'jetpack/tiled-gallery': - if ( ! empty( $block['attrs']['ids'] ) ) { - $image_ids = array_merge( $image_ids, $block['attrs']['ids'] ); - } - break; - case 'jetpack/image-compare': - if ( ! empty( $block['attrs']['beforeImageId'] ) ) { - $image_ids[] = $block['attrs']['beforeImageId']; - } - if ( ! empty( $block['attrs']['afterImageId'] ) ) { - $image_ids[] = $block['attrs']['afterImageId']; - } - break; - } - - // we could be at or over max, stop unneeded work - if ( count( $image_ids ) >= $max_images ) { - break; - } - } - - // still need to slice it because one gallery could knock us over the limit - return \array_slice( $image_ids, 0, $max_images ); - } - - /** - * Generates all Image Attachments for a Post. - * - * @return array The Image Attachments. + * @return array The Attachments. */ protected function get_attachments() { - $max_images = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) ); + // Once upon a time we only supported images, but we now support audio/video as well. + // We maintain the image-centric naming for backwards compatibility. + $max_media = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) ); - $images = array(); + if ( site_supports_blocks() && \has_blocks( $this->wp_post->post_content ) ) { + return $this->get_block_attachments( $max_media ); + } + return $this->get_classic_editor_images( $max_media ); + } + + /** + * Get media attachments from blocks. They will be formatted as ActivityPub attachments, not as WP attachments. + * + * @param int $max_media The maximum number of attachments to return. + * + * @return array The attachments. + */ + protected function get_block_attachments( $max_media ) { + // max media can't be negative or zero + if ( $max_media <= 0 ) { + return array(); + } + + $id = $this->wp_post->ID; + + $media_ids = array(); + + // list post thumbnail first if this post has one + if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) { + $media_ids[] = \get_post_thumbnail_id( $id ); + } + + if ( $max_media > 0 ) { + $blocks = \parse_blocks( $this->wp_post->post_content ); + $media_ids = self::get_media_ids_from_blocks( $blocks, $media_ids, $max_media ); + } + $media_ids = \array_unique( $media_ids ); + + return \array_filter( \array_map( array( self::class, 'wp_attachment_to_activity_attachment' ), $media_ids ) ); + } + + /** + * Get image attachments from the classic editor. + * Note that audio/video attachments are only supported in the block editor. + * + * @param int $max_images The maximum number of images to return. + * + * @return array The attachments. + */ + protected function get_classic_editor_images( $max_images ) { // max images can't be negative or zero if ( $max_images <= 0 ) { - return $images; + return array(); } $id = $this->wp_post->ID; @@ -228,68 +215,144 @@ class Post { } if ( $max_images > 0 ) { - // first try to get images that are actually in the post content - if ( site_supports_blocks() && \has_blocks( $this->wp_post->post_content ) ) { - $block_image_ids = $this->get_block_image_ids( $max_images, $image_ids ); - $image_ids = \array_merge( $image_ids, $block_image_ids ); - } else { - // fallback to images attached to the post - $query = new \WP_Query( - array( - 'post_parent' => $id, - 'post_status' => 'inherit', - 'post_type' => 'attachment', - 'post_mime_type' => 'image', - 'order' => 'ASC', - 'orderby' => 'menu_order ID', - 'posts_per_page' => $max_images, - ) - ); - foreach ( $query->get_posts() as $attachment ) { - if ( ! \in_array( $attachment->ID, $image_ids, true ) ) { - $image_ids[] = $attachment->ID; - } + $query = new \WP_Query( + array( + 'post_parent' => $id, + 'post_status' => 'inherit', + 'post_type' => 'attachment', + 'post_mime_type' => 'image', + 'order' => 'ASC', + 'orderby' => 'menu_order ID', + 'posts_per_page' => $max_images, + ) + ); + foreach ( $query->get_posts() as $attachment ) { + if ( ! \in_array( $attachment->ID, $image_ids, true ) ) { + $image_ids[] = $attachment->ID; } } } - $image_ids = \array_unique( $image_ids ); - // get URLs for each image - foreach ( $image_ids as $id ) { - $image_size = 'full'; + return \array_filter( \array_map( array( self::class, 'wp_attachment_to_activity_attachment' ), $image_ids ) ); + } - /** - * Filter the image URL returned for each post. - * - * @param array|false $thumbnail The image URL, or false if no image is available. - * @param int $id The attachment ID. - * @param string $image_size The image size to retrieve. Set to 'full' by default. - */ - $thumbnail = apply_filters( - 'activitypub_get_image', - $this->get_image( $id, $image_size ), - $id, - $image_size - ); + /** + * Recursively get media IDs from blocks. + * @param array $blocks The blocks to search for media IDs + * @param array $media_ids The media IDs to append new IDs to + * @param int $max_media The maximum number of media to return. + * + * @return array The image IDs. + */ + protected static function get_media_ids_from_blocks( $blocks, $media_ids, $max_media ) { - if ( $thumbnail ) { - $mimetype = \get_post_mime_type( $id ); - $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true ); - $image = array( - 'type' => 'Image', - 'url' => $thumbnail[0], - 'mediaType' => $mimetype, - ); + foreach ( $blocks as $block ) { + // recurse into inner blocks + if ( ! empty( $block['innerBlocks'] ) ) { + $media_ids = self::get_media_ids_from_blocks( $block['innerBlocks'], $media_ids, $max_media ); + } - if ( $alt ) { - $image['name'] = $alt; - } - $images[] = $image; + switch ( $block['blockName'] ) { + case 'core/image': + case 'core/cover': + case 'core/audio': + case 'core/video': + case 'videopress/video': + if ( ! empty( $block['attrs']['id'] ) ) { + $media_ids[] = $block['attrs']['id']; + } + break; + case 'jetpack/slideshow': + case 'jetpack/tiled-gallery': + if ( ! empty( $block['attrs']['ids'] ) ) { + $media_ids = array_merge( $media_ids, $block['attrs']['ids'] ); + } + break; + case 'jetpack/image-compare': + if ( ! empty( $block['attrs']['beforeImageId'] ) ) { + $media_ids[] = $block['attrs']['beforeImageId']; + } + if ( ! empty( $block['attrs']['afterImageId'] ) ) { + $media_ids[] = $block['attrs']['afterImageId']; + } + break; + } + + // stop doing unneeded work + if ( count( $media_ids ) >= $max_media ) { + break; } } - return $images; + // still need to slice it because one gallery could knock us over the limit + return array_slice( $media_ids, 0, $max_media ); + } + + /** + * Converts a WordPress Attachment to an ActivityPub Attachment. + * + * @param int $id The Attachment ID. + * + * @return array The ActivityPub Attachment. + */ + public static function wp_attachment_to_activity_attachment( $id ) { + $attachment = array(); + $mime_type = \get_post_mime_type( $id ); + $mime_type_parts = \explode( '/', $mime_type ); + // switching on image/audio/video + switch ( $mime_type_parts[0] ) { + case 'image': + $image_size = 'full'; + + /** + * Filter the image URL returned for each post. + * + * @param array|false $thumbnail The image URL, or false if no image is available. + * @param int $id The attachment ID. + * @param string $image_size The image size to retrieve. Set to 'full' by default. + */ + $thumbnail = apply_filters( + 'activitypub_get_image', + self::get_image( $id, $image_size ), + $id, + $image_size + ); + + if ( $thumbnail ) { + $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true ); + $image = array( + 'type' => 'Image', + 'url' => $thumbnail[0], + 'mediaType' => $mime_type, + ); + + if ( $alt ) { + $image['name'] = $alt; + } + $attachment = $image; + } + break; + + case 'audio': + case 'video': + $attachment = array( + 'type' => 'Document', + 'mediaType' => $mime_type, + 'url' => \wp_get_attachment_url( $id ), + 'name' => \get_the_title( $id ), + ); + $meta = wp_get_attachment_metadata( $id ); + // height and width for videos + if ( isset( $meta['width'] ) && isset( $meta['height'] ) ) { + $attachment['width'] = $meta['width']; + $attachment['height'] = $meta['height']; + } + // @todo: add `icon` support for audio/video attachments. Maybe use post thumbnail? + break; + } + + return \apply_filters( 'activitypub_attachment', $attachment, $id ); } /** @@ -300,7 +363,7 @@ class Post { * * @return array|false Array of image data, or boolean false if no image is available. */ - protected function get_image( $id, $image_size = 'full' ) { + protected static function get_image( $id, $image_size = 'full' ) { /** * Hook into the image retrieval process. Before image retrieval. * @@ -309,7 +372,7 @@ class Post { */ do_action( 'activitypub_get_image_pre', $id, $image_size ); - $thumbnail = \wp_get_attachment_image_src( $id, $image_size ); + $image = \wp_get_attachment_image_src( $id, $image_size ); /** * Hook into the image retrieval process. After image retrieval. @@ -319,7 +382,7 @@ class Post { */ do_action( 'activitypub_get_image_post', $id, $image_size ); - return $thumbnail; + return $image; } /** diff --git a/readme.txt b/readme.txt index 7e48fc1..9dc81d0 100644 --- a/readme.txt +++ b/readme.txt @@ -105,6 +105,10 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). += 1.1.0 = + +* Improved: audio and video attachments are now supported! + = 1.0.10 = * Improved: better error messages if remote profile is not accessible diff --git a/templates/settings.php b/templates/settings.php index fd80145..5ce90e1 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -138,7 +138,7 @@ - + @@ -147,13 +147,20 @@ echo \wp_kses( \sprintf( // translators: - \__( 'The number of images to attach to posts. Default: %s', 'activitypub' ), + \__( 'The number of media (images, audio, video) to attach to posts. Default: %s', 'activitypub' ), \esc_html( ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ), 'default' ); ?>

+

+ + + +

From 70cefc97122ffd60285b3848bf3dbc1e42432707 Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Fri, 27 Oct 2023 16:18:42 -0500 Subject: [PATCH 18/26] prep readme for `1.1.0` release --- README.md | 6 ++++++ readme.txt | 2 ++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index dcc91f2..b1168a8 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,12 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). +### 1.1.0 ### + +* Improved: audio and video attachments are now supported! +* Improved: better error messages if remote profile is not accessible +* Improved: PHP 8.1 compatibility + ### 1.0.10 ### * Improved: better error messages if remote profile is not accessible diff --git a/readme.txt b/readme.txt index 9dc81d0..ec1af3b 100644 --- a/readme.txt +++ b/readme.txt @@ -108,6 +108,8 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu = 1.1.0 = * Improved: audio and video attachments are now supported! +* Improved: better error messages if remote profile is not accessible +* Improved: PHP 8.1 compatibility = 1.0.10 = From eda6d6d785c34e79346025ca92a68dce1de534a7 Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Mon, 30 Oct 2023 14:32:04 -0500 Subject: [PATCH 19/26] Mentions: 1MB limit for attempting to link mentions, otherwise bail (#540) --- includes/class-mention.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/includes/class-mention.php b/includes/class-mention.php index d55e5f2..beb6246 100644 --- a/includes/class-mention.php +++ b/includes/class-mention.php @@ -26,6 +26,10 @@ class Mention { * @return string the filtered post-content */ public static function the_content( $the_content ) { + // small protection against execution timeouts: limit to 1 MB + if ( mb_strlen( $the_content ) > MB_IN_BYTES ) { + return $the_content; + } $tag_stack = array(); $protected_tags = array( 'pre', From 74a774e8e7a13d8c87e1dcde71c25ce60455aec3 Mon Sep 17 00:00:00 2001 From: Matt Wiebe Date: Wed, 1 Nov 2023 10:53:27 -0500 Subject: [PATCH 20/26] Hashtags: 1MB limit for attempting to link (#544) --- includes/class-hashtag.php | 4 ++++ readme.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/includes/class-hashtag.php b/includes/class-hashtag.php index 2d03ac4..6031d1f 100644 --- a/includes/class-hashtag.php +++ b/includes/class-hashtag.php @@ -43,6 +43,10 @@ class Hashtag { * @return string the filtered post-content */ public static function the_content( $the_content ) { + // small protection against execution timeouts: limit to 1 MB + if ( mb_strlen( $the_content ) > MB_IN_BYTES ) { + return $the_content; + } $tag_stack = array(); $protected_tags = array( 'pre', diff --git a/readme.txt b/readme.txt index ec1af3b..5a70498 100644 --- a/readme.txt +++ b/readme.txt @@ -110,6 +110,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Improved: audio and video attachments are now supported! * Improved: better error messages if remote profile is not accessible * Improved: PHP 8.1 compatibility +* Fixed: don't try to parse mentions or hashtags for very large (>1MB) posts to prevent timeouts = 1.0.10 = From 9d5bd8c22090b4e9620ff000965739191c62c0b3 Mon Sep 17 00:00:00 2001 From: Ulrich Kiermayr Date: Tue, 7 Nov 2023 00:10:54 +0100 Subject: [PATCH 21/26] More reliable way to get author and autorurl (#546) --------- Co-authored-by: Matt Wiebe --- includes/class-shortcodes.php | 6 ++++-- readme.txt | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php index 708aa61..491a6ad 100644 --- a/includes/class-shortcodes.php +++ b/includes/class-shortcodes.php @@ -390,7 +390,8 @@ class Shortcodes { return ''; } - $name = \get_the_author_meta( 'display_name', $item->post_author ); + $author_id = \get_post_field( 'post_author', $item->ID ); + $name = \get_the_author_meta( 'display_name', $author_id ); if ( ! $name ) { return ''; @@ -415,7 +416,8 @@ class Shortcodes { return ''; } - $url = \get_the_author_meta( 'user_url', $item->post_author ); + $author_id = \get_post_field( 'post_author', $item->ID ); + $url = \get_the_author_meta( 'user_url', $author_id ); if ( ! $url ) { return ''; diff --git a/readme.txt b/readme.txt index 5a70498..d59f8cd 100644 --- a/readme.txt +++ b/readme.txt @@ -111,6 +111,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Improved: better error messages if remote profile is not accessible * Improved: PHP 8.1 compatibility * Fixed: don't try to parse mentions or hashtags for very large (>1MB) posts to prevent timeouts +* Improved: more reliable [ap_author], props @uk3 = 1.0.10 = From a81e20a9bac6dcadc62979142cbb4a07586fb89f Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 7 Nov 2023 08:49:48 +0100 Subject: [PATCH 22/26] fix issue when locale is only two chars (#549) for example "de" instead of "de_DE" --- includes/transformer/class-post.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php index c10f0cf..2117ce7 100644 --- a/includes/transformer/class-post.php +++ b/includes/transformer/class-post.php @@ -82,7 +82,7 @@ class Post { $object->set_content( $this->get_content() ); $object->set_content_map( array( - \strstr( \get_locale(), '_', true ) => $this->get_content(), + $this->get_locale() => $this->get_content(), ) ); $path = sprintf( 'users/%d/followers', intval( $wp_post->post_author ) ); @@ -580,4 +580,25 @@ class Post { protected function get_mentions() { return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_post->post_content, $this->wp_post ); } + + /** + * Returns the locale of the post. + * + * @return string The locale of the post. + */ + public function get_locale() { + $post_id = $this->wp_post->ID; + $lang = \strtolower( \strtok( \get_locale(), '_-' ) ); + + /** + * Filter the locale of the post. + * + * @param string $lang The locale of the post. + * @param int $post_id The post ID. + * @param WP_Post $post The post object. + * + * @return string The filtered locale of the post. + */ + return apply_filters( 'activitypub_post_locale', $lang, $post_id, $this->wp_post ); + } } From 57b39a5c08cc6230c93507304381b8a3221e1636 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 7 Nov 2023 10:01:03 +0100 Subject: [PATCH 23/26] prepare 1.1.0 --- README.md | 5 ++++- activitypub.php | 2 +- readme.txt | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b1168a8..371ad12 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Tags:** OStatus, fediverse, activitypub, activitystream **Requires at least:** 4.7 **Tested up to:** 6.3 -**Stable tag:** 1.0.10 +**Stable tag:** 1.1.0 **Requires PHP:** 5.6 **License:** MIT **License URI:** http://opensource.org/licenses/MIT @@ -110,6 +110,9 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Improved: audio and video attachments are now supported! * Improved: better error messages if remote profile is not accessible * Improved: PHP 8.1 compatibility +* Fixed: don't try to parse mentions or hashtags for very large (>1MB) posts to prevent timeouts +* Fixed: better handling of ISO-639-1 locale codes +* Improved: more reliable [ap_author], props @uk3 ### 1.0.10 ### diff --git a/activitypub.php b/activitypub.php index 97f6067..380b438 100644 --- a/activitypub.php +++ b/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: 1.0.10 + * Version: 1.1.0 * Author: Matthias Pfefferle & Automattic * Author URI: https://automattic.com/ * License: MIT diff --git a/readme.txt b/readme.txt index d59f8cd..8958c92 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: automattic, pfefferle, mediaformat, mattwiebe, akirk, jeherve, nur Tags: OStatus, fediverse, activitypub, activitystream Requires at least: 4.7 Tested up to: 6.3 -Stable tag: 1.0.10 +Stable tag: 1.1.0 Requires PHP: 5.6 License: MIT License URI: http://opensource.org/licenses/MIT @@ -111,6 +111,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Improved: better error messages if remote profile is not accessible * Improved: PHP 8.1 compatibility * Fixed: don't try to parse mentions or hashtags for very large (>1MB) posts to prevent timeouts +* Fixed: better handling of ISO-639-1 locale codes * Improved: more reliable [ap_author], props @uk3 = 1.0.10 = From 26d0d357c22734b931331fffb14f610f384ffaca Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 7 Nov 2023 10:27:20 +0100 Subject: [PATCH 24/26] Add monthly active users (#530) * Add monthly active users for better stats on FediDB * use more optimized query thanks @mattwiebe * use transients, improve logic --------- Co-authored-by: Matt Wiebe --- includes/functions.php | 73 ++++++++++++++++++++++++++++++++ includes/rest/class-nodeinfo.php | 34 ++++----------- integration/class-nodeinfo.php | 15 +++++++ 3 files changed, 96 insertions(+), 26 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index b2972c0..ecb8765 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -5,6 +5,7 @@ use WP_Error; use Activitypub\Http; use Activitypub\Activity\Activity; use Activitypub\Collection\Followers; +use Activitypub\Collection\Users; /** * Returns the ActivityPub default JSON-context @@ -474,3 +475,75 @@ function is_json( $data ) { function is_blog_public() { return (bool) apply_filters( 'activitypub_is_blog_public', \get_option( 'blog_public', 1 ) ); } + +/** + * Get active users based on a given duration + * + * @param int $duration The duration to check in month(s) + * + * @return int The number of active users + */ +function get_active_users( $duration = 1 ) { + + $duration = intval( $duration ); + $transient_key = sprintf( 'monthly_active_users_%d', $duration ); + $count = get_transient( $transient_key ); + + if ( false === $count ) { + global $wpdb; + $query = "SELECT COUNT( DISTINCT post_author ) FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish' AND post_date <= DATE_SUB( NOW(), INTERVAL %d MONTH )"; + $query = $wpdb->prepare( $query, $duration ); + $count = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery + + set_transient( $transient_key, $count, DAY_IN_SECONDS ); + } + + // if 0 authors where active + if ( 0 === $count ) { + return 0; + } + + // if single user mode + if ( is_single_user() ) { + return 1; + } + + // if blog user is disabled + if ( is_user_disabled( Users::BLOG_USER_ID ) ) { + return $count; + } + + // also count blog user + return $count + 1; +} + +/** + * Get the total number of users + * + * @return int The total number of users + */ +function get_total_users() { + // if single user mode + if ( is_single_user() ) { + return 1; + } + + $users = \get_users( + array( + 'capability__in' => array( 'publish_posts' ), + ) + ); + + if ( is_array( $users ) ) { + $users = count( $users ); + } else { + $users = 1; + } + + // if blog user is disabled + if ( is_user_disabled( Users::BLOG_USER_ID ) ) { + return $users; + } + + return $users + 1; +} diff --git a/includes/rest/class-nodeinfo.php b/includes/rest/class-nodeinfo.php index 4829e75..62151ff 100644 --- a/includes/rest/class-nodeinfo.php +++ b/includes/rest/class-nodeinfo.php @@ -3,6 +3,8 @@ namespace Activitypub\Rest; use WP_REST_Response; +use function Activitypub\get_total_users; +use function Activitypub\get_active_users; use function Activitypub\get_rest_url_by_path; /** @@ -82,24 +84,14 @@ class Nodeinfo { 'version' => \get_bloginfo( 'version' ), ); - $users = \get_users( - array( - 'capability__in' => array( 'publish_posts' ), - ) - ); - - if ( is_countable( $users ) ) { - $users = count( $users ); - } else { - $users = 1; - } - $posts = \wp_count_posts(); $comments = \wp_count_comments(); $nodeinfo['usage'] = array( 'users' => array( - 'total' => $users, + 'total' => get_total_users(), + 'activeMonth' => get_active_users( '1 month ago' ), + 'activeHalfyear' => get_active_users( '6 month ago' ), ), 'localPosts' => (int) $posts->publish, 'localComments' => (int) $comments->approved, @@ -139,24 +131,14 @@ class Nodeinfo { 'version' => \get_bloginfo( 'version' ), ); - $users = \get_users( - array( - 'capability__in' => array( 'publish_posts' ), - ) - ); - - if ( is_countable( $users ) ) { - $users = count( $users ); - } else { - $users = 1; - } - $posts = \wp_count_posts(); $comments = \wp_count_comments(); $nodeinfo['usage'] = array( 'users' => array( - 'total' => (int) $users, + 'total' => get_total_users(), + 'activeMonth' => get_active_users( 1 ), + 'activeHalfyear' => get_active_users( 6 ), ), 'localPosts' => (int) $posts->publish, 'localComments' => (int) $comments->approved, diff --git a/integration/class-nodeinfo.php b/integration/class-nodeinfo.php index f1e2506..dea6c67 100644 --- a/integration/class-nodeinfo.php +++ b/integration/class-nodeinfo.php @@ -1,6 +1,9 @@ get_total_users(), + 'activeMonth' => get_active_users( '1 month ago' ), + 'activeHalfyear' => get_active_users( '6 month ago' ), + ); + return $nodeinfo; } @@ -44,6 +53,12 @@ class Nodeinfo { public static function add_nodeinfo2_discovery( $nodeinfo ) { $nodeinfo['protocols'][] = 'activitypub'; + $nodeinfo['usage']['users'] = array( + 'total' => get_total_users(), + 'activeMonth' => get_active_users( '1 month ago' ), + 'activeHalfyear' => get_active_users( '6 month ago' ), + ); + return $nodeinfo; } } From d8a2d75768245cd68e90c6e3b59f562df1ea2371 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 7 Nov 2023 11:02:40 +0100 Subject: [PATCH 25/26] update changelog --- README.md | 1 + readme.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 371ad12..ecde873 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Fixed: don't try to parse mentions or hashtags for very large (>1MB) posts to prevent timeouts * Fixed: better handling of ISO-639-1 locale codes * Improved: more reliable [ap_author], props @uk3 +* Improved: NodeInfo statistics ### 1.0.10 ### diff --git a/readme.txt b/readme.txt index 8958c92..ef8a14a 100644 --- a/readme.txt +++ b/readme.txt @@ -113,6 +113,7 @@ Project maintained on GitHub at [automattic/wordpress-activitypub](https://githu * Fixed: don't try to parse mentions or hashtags for very large (>1MB) posts to prevent timeouts * Fixed: better handling of ISO-639-1 locale codes * Improved: more reliable [ap_author], props @uk3 +* Improved: NodeInfo statistics = 1.0.10 = From 1437b5acd80fa12d0accf3ccf450322aa226fe12 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 7 Nov 2023 11:19:42 +0100 Subject: [PATCH 26/26] prepare compatibility with WP 6.4 --- README.md | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ecde873..4c41fcd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Contributors:** [automattic](https://profiles.wordpress.org/automattic/), [pfefferle](https://profiles.wordpress.org/pfefferle/), [mediaformat](https://profiles.wordpress.org/mediaformat/), [mattwiebe](https://profiles.wordpress.org/mattwiebe/), [akirk](https://profiles.wordpress.org/akirk/), [jeherve](https://profiles.wordpress.org/jeherve/), [nuriapena](https://profiles.wordpress.org/nuriapena/), [cavalierlife](https://profiles.wordpress.org/cavalierlife/) **Tags:** OStatus, fediverse, activitypub, activitystream **Requires at least:** 4.7 -**Tested up to:** 6.3 +**Tested up to:** 6.4 **Stable tag:** 1.1.0 **Requires PHP:** 5.6 **License:** MIT diff --git a/readme.txt b/readme.txt index ef8a14a..eed006a 100644 --- a/readme.txt +++ b/readme.txt @@ -2,7 +2,7 @@ Contributors: automattic, pfefferle, mediaformat, mattwiebe, akirk, jeherve, nuriapena, cavalierlife Tags: OStatus, fediverse, activitypub, activitystream Requires at least: 4.7 -Tested up to: 6.3 +Tested up to: 6.4 Stable tag: 1.1.0 Requires PHP: 5.6 License: MIT