diff --git a/README.md b/README.md index d1b4461..d7a9662 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Contributors:** [pfefferle](https://profiles.wordpress.org/pfefferle/), [mediaformat](https://profiles.wordpress.org/mediaformat/), [akirk](https://profiles.wordpress.org/akirk/), [automattic](https://profiles.wordpress.org/automattic/) **Tags:** OStatus, fediverse, activitypub, activitystream **Requires at least:** 4.7 -**Tested up to:** 6.1 +**Tested up to:** 6.2 **Stable tag:** 0.17.0 **Requires PHP:** 5.6 **License:** MIT @@ -111,7 +111,11 @@ Where 'blog' is the path to the subdirectory at which your blog resides. ## Changelog ## -Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub). +Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub). + +### Next ### + +* Compatibility: indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.2. ### 0.17.0 ### diff --git a/activitypub.php b/activitypub.php index c5275ba..2ee311b 100644 --- a/activitypub.php +++ b/activitypub.php @@ -23,13 +23,16 @@ function init() { \defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 ); \defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=
)|(?<= [ap_title] [ap_hashtags] [ap_shortlink]
+
)|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' );
\defined( 'ACTIVITYPUB_USERNAME_REGEXP' ) || \define( 'ACTIVITYPUB_USERNAME_REGEXP', '(?:([A-Za-z0-9_-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))' );
- \defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '' );
\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "
@@ -261,19 +143,11 @@ function get_identifier_settings( $user_id ) {
}
function get_followers( $user_id ) {
- $followers = \Activitypub\Peer\Followers::get_followers( $user_id );
-
- if ( ! $followers ) {
- return array();
- }
-
- return $followers;
+ return Collection\Followers::get_followers( $user_id );
}
function count_followers( $user_id ) {
- $followers = \Activitypub\get_followers( $user_id );
-
- return \count( $followers );
+ return Collection\Followers::count_followers( $user_id );
}
/**
@@ -323,3 +197,38 @@ function url_to_authorid( $url ) {
return 0;
}
+
+/**
+ * Return the custom Activity Pub description, if set, or default author description.
+ *
+ * @param int $user_id The user ID.
+ * @return string
+ */
+function get_author_description( $user_id ) {
+ $description = get_user_meta( $user_id, 'activitypub_user_description', true );
+ if ( empty( $description ) ) {
+ $description = get_user_meta( $user_id, 'description', true );
+ }
+ return $description;
+}
+
+/**
+ * Check for Tombstone Objects
+ *
+ * @see https://www.w3.org/TR/activitypub/#delete-activity-outbox
+ *
+ * @param WP_Error $wp_error A WP_Error-Response of an HTTP-Request
+ *
+ * @return boolean true if HTTP-Code is 410 or 404
+ */
+function is_tombstone( $wp_error ) {
+ if ( ! is_wp_error( $wp_error ) ) {
+ return false;
+ }
+
+ if ( in_array( (int) $wp_error->get_error_code(), array( 404, 410 ), true ) ) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/includes/model/class-follower.php b/includes/model/class-follower.php
new file mode 100644
index 0000000..3a15690
--- /dev/null
+++ b/includes/model/class-follower.php
@@ -0,0 +1,317 @@
+errors
+ *
+ * @var string
+ */
+ private $error;
+
+ /**
+ * A list of errors
+ *
+ * @var array
+ */
+ private $errors;
+
+ /**
+ * Maps the meta fields to the local db fields
+ *
+ * @var array
+ */
+ private $map_meta = array(
+ 'name' => 'name',
+ 'preferredUsername' => 'username',
+ 'inbox' => 'inbox',
+ );
+
+ /**
+ * Constructor
+ *
+ * @param WP_Post $post
+ */
+ public function __construct( $actor ) {
+ $this->actor = $actor;
+
+ $term = get_term_by( 'name', $actor, Followers::TAXONOMY );
+
+ if ( $term ) {
+ $this->id = $term->term_id;
+ $this->slug = $term->slug;
+ $this->meta = json_decode( $term->meta );
+ }
+ }
+
+ /**
+ * Magic function to implement getter and setter
+ *
+ * @param string $method
+ * @param string $params
+ *
+ * @return void
+ */
+ public function __call( $method, $params ) {
+ $var = \strtolower( \substr( $method, 4 ) );
+
+ if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
+ if ( empty( $this->$var ) ) {
+ return $this->get( $var );
+ }
+ return $this->$var;
+ }
+
+ if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
+ $this->$var = $params[0];
+ }
+ }
+
+ public function from_meta( $meta ) {
+ $this->meta = $meta;
+
+ foreach ( $this->map_meta as $remote => $internal ) {
+ if ( ! empty( $meta[ $remote ] ) ) {
+ $this->$internal = $meta[ $remote ];
+ }
+ }
+
+ if ( ! empty( $meta['icon']['url'] ) ) {
+ $this->avatar = $meta['icon']['url'];
+ }
+
+ if ( ! empty( $meta['endpoints']['sharedInbox'] ) ) {
+ $this->shared_inbox = $meta['endpoints']['sharedInbox'];
+ } elseif ( ! empty( $meta['inbox'] ) ) {
+ $this->shared_inbox = $meta['inbox'];
+ }
+
+ $this->updated_at = \strtotime( 'now' );
+ }
+
+ public function get( $attribute ) {
+ if ( $this->$attribute ) {
+ return $this->$attribute;
+ }
+
+ $attribute = get_term_meta( $this->id, $attribute, true );
+ if ( $attribute ) {
+ $this->$attribute = $attribute;
+ return $attribute;
+ }
+
+ $attribute = $this->get_meta_by( $attribute );
+ if ( $attribute ) {
+ $this->$attribute = $attribute;
+ return $attribute;
+ }
+
+ return null;
+ }
+
+ public function get_errors() {
+ if ( $this->errors ) {
+ return $this->errors;
+ }
+
+ $this->errors = get_term_meta( $this->id, 'errors' );
+ return $this->errors;
+ }
+
+ public function count_errors() {
+ $errors = $this->get_errors();
+
+ if ( is_array( $errors ) && ! empty( $errors ) ) {
+ return count( $errors );
+ }
+
+ return 0;
+ }
+
+ public function get_latest_error_message() {
+ $errors = $this->get_errors();
+
+ if ( is_array( $errors ) && ! empty( $errors ) ) {
+ return reset( $errors );
+ }
+
+ return '';
+ }
+
+ public function get_meta_by( $attribute ) {
+ $meta = $this->get_meta();
+
+ // try mapped data (see $this->map_meta)
+ foreach ( $this->map_meta as $remote => $local ) {
+ if ( $attribute === $local && isset( $meta[ $remote ] ) ) {
+ return $meta[ $remote ];
+ }
+ }
+
+ // try ActivityPub attribtes
+ if ( ! empty( $this->map_meta[ $attribute ] ) ) {
+ return $this->map_meta[ $attribute ];
+ }
+
+ return null;
+ }
+
+ public function get_meta() {
+ if ( $this->meta ) {
+ return $this->meta;
+ }
+
+ return null;
+ }
+
+ public function update() {
+ $term = wp_update_term(
+ $this->id,
+ Followers::TAXONOMY,
+ array(
+ 'description' => wp_json_encode( $this->get_meta( true ) ),
+ )
+ );
+
+ $this->updated_at = \strtotime( 'now' );
+ $this->update_term_meta();
+ }
+
+ public function save() {
+ $term = wp_insert_term(
+ $this->actor,
+ Followers::TAXONOMY,
+ array(
+ 'slug' => sanitize_title( $this->get_actor() ),
+ 'description' => wp_json_encode( $this->get_meta() ),
+ )
+ );
+
+ $this->id = $term['term_id'];
+
+ $this->update_term_meta();
+ }
+
+ public function upsert() {
+ if ( $this->id ) {
+ $this->update();
+ } else {
+ $this->save();
+ }
+ }
+
+ protected function update_term_meta() {
+ $attributes = array( 'inbox', 'shared_inbox', 'avatar', 'updated_at', 'name', 'username' );
+
+ foreach ( $attributes as $attribute ) {
+ if ( $this->get( $attribute ) ) {
+ update_term_meta( $this->id, $attribute, $this->get( $attribute ) );
+ }
+ }
+
+ if ( $this->error ) {
+ if ( is_string( $this->error ) ) {
+ $error = $this->error;
+ } elseif ( is_wp_error( $this->error ) ) {
+ $error = $this->error->get_error_message();
+ } else {
+ $error = __( 'Unknown Error or misconfigured Error-Message', 'activitypub' );
+ }
+
+ add_term_meta( $this->id, 'errors', $error );
+ }
+
+ }
+}
diff --git a/includes/model/class-post.php b/includes/model/class-post.php
index cfe49b6..2b6c84c 100644
--- a/includes/model/class-post.php
+++ b/includes/model/class-post.php
@@ -28,6 +28,13 @@ class Post {
*/
private $id;
+ /**
+ * The Object URL.
+ *
+ * @var string
+ */
+ private $url;
+
/**
* The Object Summary.
*
@@ -179,6 +186,7 @@ class Post {
$array = array(
'id' => $this->get_id(),
+ 'url' => $this->get_url(),
'type' => $this->get_object_type(),
'published' => \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_date_gmt ) ),
'attributedTo' => \get_author_posts_url( $post->post_author ),
@@ -207,13 +215,13 @@ class Post {
}
/**
- * Returns the ID of an Activity Object
+ * Returns the URL of an Activity Object
*
* @return string
*/
- public function get_id() {
- if ( $this->id ) {
- return $this->id;
+ public function get_url() {
+ if ( $this->url ) {
+ return $this->url;
}
$post = $this->post;
@@ -224,11 +232,26 @@ class Post {
$permalink = \get_permalink( $post );
}
- $this->id = $permalink;
+ $this->url = $permalink;
return $permalink;
}
+ /**
+ * Returns the ID of an Activity Object
+ *
+ * @return string
+ */
+ public function get_id() {
+ if ( $this->id ) {
+ return $this->id;
+ }
+
+ $this->id = $this->get_url();
+
+ return $this->id;
+ }
+
/**
* Returns a list of Image Attachments
*
@@ -282,16 +305,31 @@ class Post {
// get URLs for each image
foreach ( $image_ids as $id ) {
- $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
- $thumbnail = \wp_get_attachment_image_src( $id, 'full' );
- $mimetype = \get_post_mime_type( $id );
+ $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',
+ $this->get_image( $id, $image_size ),
+ $id,
+ $image_size
+ );
if ( $thumbnail ) {
- $image = array(
- 'type' => 'Image',
- 'url' => $thumbnail[0],
+ $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,
);
+
if ( $alt ) {
$image['name'] = $alt;
}
@@ -304,6 +342,36 @@ class Post {
return $images;
}
+ /**
+ * Return details about an image attachment.
+ *
+ * @param int $id The attachment ID.
+ * @param string $image_size The image size to retrieve. Set to 'full' by default.
+ *
+ * @return array|false Array of image data, or boolean false if no image is available.
+ */
+ public function get_image( $id, $image_size = 'full' ) {
+ /**
+ * Hook into the image retrieval process. Before image retrieval.
+ *
+ * @param int $id The attachment ID.
+ * @param string $image_size The image size to retrieve. Set to 'full' by default.
+ */
+ do_action( 'activitypub_get_image_pre', $id, $image_size );
+
+ $thumbnail = \wp_get_attachment_image_src( $id, $image_size );
+
+ /**
+ * Hook into the image retrieval process. After image retrieval.
+ *
+ * @param int $id The attachment ID.
+ * @param string $image_size The image size to retrieve. Set to 'full' by default.
+ */
+ do_action( 'activitypub_get_image_pre', $id, $image_size );
+
+ return $thumbnail;
+ }
+
/**
* Returns a list of Tags, used in the Post
*
@@ -463,48 +531,6 @@ class Post {
return "[ap_content]\n\n[ap_hashtags]\n\n[ap_permalink type=\"html\"]";
}
- // Upgrade from old template codes to shortcodes.
- $content = self::upgrade_post_content_template();
-
- return $content;
- }
-
- /**
- * Updates the custom template to use shortcodes instead of the deprecated templates.
- *
- * @return string the updated template content
- */
- public static function upgrade_post_content_template() {
- // Get the custom template.
- $old_content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
-
- // If the old content exists but is a blank string, we're going to need a flag to updated it even
- // after setting it to the default contents.
- $need_update = false;
-
- // If the old contents is blank, use the defaults.
- if ( '' === $old_content ) {
- $old_content = ACTIVITYPUB_CUSTOM_POST_CONTENT;
- $need_update = true;
- }
-
- // Set the new content to be the old content.
- $content = $old_content;
-
- // Convert old templates to shortcodes.
- $content = \str_replace( '%title%', '[ap_title]', $content );
- $content = \str_replace( '%excerpt%', '[ap_excerpt]', $content );
- $content = \str_replace( '%content%', '[ap_content]', $content );
- $content = \str_replace( '%permalink%', '[ap_permalink type="html"]', $content );
- $content = \str_replace( '%shortlink%', '[ap_shortlink type="html"]', $content );
- $content = \str_replace( '%hashtags%', '[ap_hashtags]', $content );
- $content = \str_replace( '%tags%', '[ap_hashtags]', $content );
-
- // Store the new template if required.
- if ( $content !== $old_content || $need_update ) {
- \update_option( 'activitypub_custom_post_content', $content );
- }
-
return $content;
}
}
diff --git a/includes/peer/class-followers.php b/includes/peer/class-followers.php
index abc18e3..e0e6ddb 100644
--- a/includes/peer/class-followers.php
+++ b/includes/peer/class-followers.php
@@ -9,76 +9,26 @@ namespace Activitypub\Peer;
class Followers {
public static function get_followers( $author_id ) {
- $followers = \get_user_option( 'activitypub_followers', $author_id );
+ _deprecated_function( __METHOD__, '1.0.0', '\Activitypub\Collection\Followers::get_followers' );
- if ( ! $followers ) {
- return array();
- }
-
- foreach ( $followers as $key => $follower ) {
- if (
- \is_array( $follower ) &&
- isset( $follower['type'] ) &&
- 'Person' === $follower['type'] &&
- isset( $follower['id'] ) &&
- false !== \filter_var( $follower['id'], \FILTER_VALIDATE_URL )
- ) {
- $followers[ $key ] = $follower['id'];
- }
- }
-
- return $followers;
+ return \Activitypub\Collection\Followers::get_followers( $author_id );
}
public static function count_followers( $author_id ) {
- $followers = self::get_followers( $author_id );
+ _deprecated_function( __METHOD__, '1.0.0', '\Activitypub\Collection\Followers::count_followers' );
- return \count( $followers );
+ return \Activitypub\Collection\Followers::count_followers( $author_id );
}
public static function add_follower( $actor, $author_id ) {
- $followers = \get_user_option( 'activitypub_followers', $author_id );
+ _deprecated_function( __METHOD__, '1.0.0', '\Activitypub\Collection\Followers::add_follower' );
- if ( ! \is_string( $actor ) ) {
- if (
- \is_array( $actor ) &&
- isset( $actor['type'] ) &&
- 'Person' === $actor['type'] &&
- isset( $actor['id'] ) &&
- false !== \filter_var( $actor['id'], \FILTER_VALIDATE_URL )
- ) {
- $actor = $actor['id'];
- }
-
- return new \WP_Error(
- 'invalid_actor_object',
- \__( 'Unknown Actor schema', 'activitypub' ),
- array(
- 'status' => 404,
- )
- );
- }
-
- if ( ! \is_array( $followers ) ) {
- $followers = array( $actor );
- } else {
- $followers[] = $actor;
- }
-
- $followers = \array_unique( $followers );
-
- \update_user_meta( $author_id, 'activitypub_followers', $followers );
+ return \Activitypub\Collection\Followers::add_follower( $author_id, $actor );
}
public static function remove_follower( $actor, $author_id ) {
- $followers = \get_user_option( 'activitypub_followers', $author_id );
+ _deprecated_function( __METHOD__, '1.0.0', '\Activitypub\Collection\Followers::remove_follower' );
- foreach ( $followers as $key => $value ) {
- if ( $value === $actor ) {
- unset( $followers[ $key ] );
- }
- }
-
- \update_user_meta( $author_id, 'activitypub_followers', $followers );
+ return \Activitypub\Collection\Followers::remove_follower( $author_id, $actor );
}
}
diff --git a/includes/rest/class-followers.php b/includes/rest/class-followers.php
index 3b1e146..9a8b9b6 100644
--- a/includes/rest/class-followers.php
+++ b/includes/rest/class-followers.php
@@ -1,6 +1,12 @@
\d+)/followers',
array(
array(
- 'methods' => \WP_REST_Server::READABLE,
+ 'methods' => WP_REST_Server::READABLE,
'callback' => array( self::class, 'get' ),
'args' => self::request_parameters(),
'permission_callback' => '__return_true',
@@ -46,7 +52,7 @@ class Followers {
$user = \get_user_by( 'ID', $user_id );
if ( ! $user ) {
- return new \WP_Error(
+ return new WP_Error(
'rest_invalid_param',
\__( 'User not found', 'activitypub' ),
array(
@@ -63,7 +69,7 @@ class Followers {
*/
\do_action( 'activitypub_outbox_pre' );
- $json = new \stdClass();
+ $json = new stdClass();
$json->{'@context'} = \Activitypub\get_context();
@@ -73,14 +79,11 @@ class Followers {
$json->type = 'OrderedCollectionPage';
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/followers" ); // phpcs:ignore
- $json->totalItems = \Activitypub\count_followers( $user_id ); // phpcs:ignore
- $json->orderedItems = \Activitypub\Peer\Followers::get_followers( $user_id ); // phpcs:ignore
-
$json->first = $json->partOf; // phpcs:ignore
+ $json->totalItems = FollowerCollection::count_followers( $user_id ); // phpcs:ignore
+ $json->orderedItems = FollowerCollection::get_followers( $user_id, ARRAY_N ); // phpcs:ignore
- $json->first = \get_rest_url( null, "/activitypub/1.0/users/$user_id/followers" );
-
- $response = new \WP_REST_Response( $json, 200 );
+ $response = new WP_REST_Response( $json, 200 );
$response->header( 'Content-Type', 'application/activity+json' );
return $response;
diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php
index cfc7677..ba1fc0a 100644
--- a/includes/rest/class-inbox.php
+++ b/includes/rest/class-inbox.php
@@ -19,10 +19,8 @@ class Inbox {
*/
public static function init() {
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
- \add_action( 'activitypub_inbox_follow', array( self::class, 'handle_follow' ), 10, 2 );
- \add_action( 'activitypub_inbox_undo', array( self::class, 'handle_unfollow' ), 10, 2 );
- //\add_action( 'activitypub_inbox_like', array( self::class, 'handle_reaction' ), 10, 2 );
- //\add_action( 'activitypub_inbox_announce', array( self::class, 'handle_reaction' ), 10, 2 );
+ \add_filter( 'rest_pre_serve_request', array( self::class, 'serve_request' ), 11, 4 );
+
\add_action( 'activitypub_inbox_create', array( self::class, 'handle_create' ), 10, 2 );
}
@@ -319,43 +317,6 @@ class Inbox {
return $params;
}
- /**
- * Handles "Follow" requests
- *
- * @param array $object The activity-object
- * @param int $user_id The id of the local blog-user
- */
- public static function handle_follow( $object, $user_id ) {
- // save follower
- Followers::add_follower( $object['actor'], $user_id );
-
- // get inbox
- $inbox = \Activitypub\get_inbox_by_actor( $object['actor'] );
-
- // send "Accept" activity
- $activity = new Activity( 'Accept' );
- $activity->set_object( $object );
- $activity->set_actor( \get_author_posts_url( $user_id ) );
- $activity->set_to( $object['actor'] );
- $activity->set_id( \get_author_posts_url( $user_id ) . '#follow-' . \preg_replace( '~^https?://~', '', $object['actor'] ) );
-
- $activity = $activity->to_simple_json();
-
- $response = \Activitypub\safe_remote_post( $inbox, $activity, $user_id );
- }
-
- /**
- * Handles "Unfollow" requests
- *
- * @param array $object The activity-object
- * @param int $user_id The id of the local blog-user
- */
- public static function handle_unfollow( $object, $user_id ) {
- if ( isset( $object['object'] ) && isset( $object['object']['type'] ) && 'Follow' === $object['object']['type'] ) {
- Followers::remove_follower( $object['actor'], $user_id );
- }
- }
-
/**
* Handles "Reaction" requests
*
diff --git a/includes/rest/class-nodeinfo.php b/includes/rest/class-nodeinfo.php
index 100fdd3..980c24b 100644
--- a/includes/rest/class-nodeinfo.php
+++ b/includes/rest/class-nodeinfo.php
@@ -75,13 +75,24 @@ class Nodeinfo {
'version' => \get_bloginfo( 'version' ),
);
- $users = \count_users();
+ $users = \get_users(
+ array(
+ 'capability__in' => array( 'publish_posts' ),
+ )
+ );
+
+ if ( is_array( $users ) ) {
+ $users = count( $users );
+ } else {
+ $users = 1;
+ }
+
$posts = \wp_count_posts();
$comments = \wp_count_comments();
$nodeinfo['usage'] = array(
'users' => array(
- 'total' => (int) $users['total_users'],
+ 'total' => $users,
),
'localPosts' => (int) $posts->publish,
'localComments' => (int) $comments->approved,
diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger.php
index d41cf25..10dcfa4 100644
--- a/includes/rest/class-webfinger.php
+++ b/includes/rest/class-webfinger.php
@@ -54,15 +54,16 @@ class Webfinger {
$resource = \str_replace( 'acct:', '', $resource );
$resource_identifier = \substr( $resource, 0, \strrpos( $resource, '@' ) );
- $resource_host = \substr( \strrchr( $resource, '@' ), 1 );
+ $resource_host = \str_replace( 'www.', '', \substr( \strrchr( $resource, '@' ), 1 ) );
+ $blog_host = \str_replace( 'www.', '', \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) );
- if ( \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) !== $resource_host ) {
+ if ( $blog_host !== $resource_host ) {
return new WP_Error( 'activitypub_wrong_host', \__( 'Resource host does not match blog host', 'activitypub' ), array( 'status' => 404 ) );
}
$user = \get_user_by( 'login', \esc_sql( $resource_identifier ) );
- if ( ! $user || ! user_can( $user, 'publish_posts' ) ) {
+ if ( ! $user || ! \user_can( $user, 'publish_posts' ) ) {
return new WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) );
}
diff --git a/includes/table/class-followers.php b/includes/table/class-followers.php
new file mode 100644
index 0000000..c9b5948
--- /dev/null
+++ b/includes/table/class-followers.php
@@ -0,0 +1,117 @@
+ '',
+ 'avatar' => \__( 'Avatar', 'activitypub' ),
+ 'name' => \__( 'Name', 'activitypub' ),
+ 'username' => \__( 'Username', 'activitypub' ),
+ 'identifier' => \__( 'Identifier', 'activitypub' ),
+ 'errors' => \__( 'Errors', 'activitypub' ),
+ 'latest-error' => \__( 'Latest Error Message', 'activitypub' ),
+ );
+ }
+
+ public function get_sortable_columns() {
+ return array();
+ }
+
+ public function prepare_items() {
+ $columns = $this->get_columns();
+ $hidden = array();
+
+ $this->process_action();
+ $this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() );
+
+ $page_num = $this->get_pagenum();
+ $per_page = 20;
+
+ $follower = FollowerCollection::get_followers( \get_current_user_id(), ACTIVITYPUB_OBJECT, $per_page, ( $page_num - 1 ) * $per_page );
+ $counter = FollowerCollection::count_followers( \get_current_user_id() );
+
+ $this->items = array();
+ $this->set_pagination_args(
+ array(
+ 'total_items' => $counter,
+ 'total_pages' => round( $counter / $per_page ),
+ 'per_page' => $per_page,
+ )
+ );
+
+ foreach ( $follower as $follower ) {
+ $item = array(
+ 'avatar' => esc_attr( $follower->get_avatar() ),
+ 'name' => esc_attr( $follower->get_name() ),
+ 'username' => esc_attr( $follower->get_username() ),
+ 'identifier' => esc_attr( $follower->get_actor() ),
+ 'errors' => $follower->count_errors(),
+ 'latest-error' => $follower->get_latest_error_message(),
+ );
+
+ $this->items[] = $item;
+ }
+ }
+
+ public function get_bulk_actions() {
+ return array(
+ 'delete' => __( 'Delete', 'activitypub' ),
+ );
+ }
+
+ public function column_default( $item, $column_name ) {
+ if ( ! array_key_exists( $column_name, $item ) ) {
+ return __( 'None', 'activitypub' );
+ }
+ return $item[ $column_name ];
+ }
+
+ public function column_avatar( $item ) {
+ return sprintf(
+ '',
+ $item['avatar']
+ );
+ }
+
+ public function column_identifier( $item ) {
+ return sprintf(
+ '%s',
+ $item['identifier'],
+ $item['identifier']
+ );
+ }
+
+ public function column_cb( $item ) {
+ return sprintf( '', esc_attr( $item['identifier'] ) );
+ }
+
+ public function process_action() {
+ if ( ! isset( $_REQUEST['followers'] ) || ! isset( $_REQUEST['_apnonce'] ) ) {
+ return false;
+ }
+
+ if ( ! wp_verify_nonce( $_REQUEST['_apnonce'], 'activitypub-followers-list' ) ) {
+ return false;
+ }
+
+ if ( ! current_user_can( 'edit_user', \get_current_user_id() ) ) {
+ return false;
+ }
+
+ $followers = $_REQUEST['followers']; // phpcs:ignore
+
+ switch ( $this->current_action() ) {
+ case 'delete':
+ FollowerCollection::remove_follower( \get_current_user_id(), $followers );
+ break;
+ }
+ }
+}
diff --git a/includes/table/followers-list.php b/includes/table/followers-list.php
deleted file mode 100644
index 81444ee..0000000
--- a/includes/table/followers-list.php
+++ /dev/null
@@ -1,36 +0,0 @@
- \__( 'Identifier', 'activitypub' ),
- );
- }
-
- public function get_sortable_columns() {
- return array();
- }
-
- public function prepare_items() {
- $columns = $this->get_columns();
- $hidden = array();
-
- $this->process_action();
- $this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() );
-
- $this->items = array();
-
- foreach ( \Activitypub\Peer\Followers::get_followers( \get_current_user_id() ) as $follower ) {
- $this->items[]['identifier'] = \esc_attr( $follower );
- }
- }
-
- public function column_default( $item, $column_name ) {
- return $item[ $column_name ];
- }
-}
diff --git a/readme.txt b/readme.txt
index 9c7661d..0004235 100644
--- a/readme.txt
+++ b/readme.txt
@@ -2,7 +2,7 @@
Contributors: pfefferle, mediaformat, akirk, automattic
Tags: OStatus, fediverse, activitypub, activitystream
Requires at least: 4.7
-Tested up to: 6.1
+Tested up to: 6.2
Stable tag: 0.17.0
Requires PHP: 5.6
License: MIT
@@ -111,7 +111,12 @@ Where 'blog' is the path to the subdirectory at which your blog resides.
== Changelog ==
-Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
+Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub).
+
+= Next =
+
+* Compatibility: add hooks to allow modifying images returned in ActivityPub requests.
+* Compatibility: indicate that the plugin is compatible and has been tested with the latest version of WordPress, 6.2.
= 0.17.0 =
diff --git a/templates/author-json.php b/templates/author-json.php
index 1003989..3b355b9 100644
--- a/templates/author-json.php
+++ b/templates/author-json.php
@@ -8,7 +8,7 @@ $json->id = \get_author_posts_url( $author_id );
$json->type = 'Person';
$json->name = \get_the_author_meta( 'display_name', $author_id );
$json->summary = \html_entity_decode(
- \get_the_author_meta( 'description', $author_id ),
+ \Activitypub\get_author_description( $author_id ),
\ENT_QUOTES,
'UTF-8'
);
diff --git a/templates/followers-list.php b/templates/followers-list.php
index 057f498..c79c961 100644
--- a/templates/followers-list.php
+++ b/templates/followers-list.php
@@ -1,16 +1,17 @@
+
+
diff --git a/tests/test-class-activitypub-activity-dispatcher.php b/tests/test-class-activitypub-activity-dispatcher.php
index 0e503e5..bde52ab 100644
--- a/tests/test-class-activitypub-activity-dispatcher.php
+++ b/tests/test-class-activitypub-activity-dispatcher.php
@@ -5,17 +5,22 @@ class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HT
'url' => 'https://example.org/users/username',
'inbox' => 'https://example.org/users/username/inbox',
'name' => 'username',
+ 'prefferedUsername' => 'username',
),
'jon@example.com' => array(
'url' => 'https://example.com/author/jon',
'inbox' => 'https://example.com/author/jon/inbox',
'name' => 'jon',
+ 'prefferedUsername' => 'jon',
),
);
public function test_dispatch_activity() {
$followers = array( 'https://example.com/author/jon', 'https://example.org/users/username' );
- \update_user_meta( 1, 'activitypub_followers', $followers );
+
+ foreach ( $followers as $follower ) {
+ \Activitypub\Collection\Followers::add_follower( 1, $follower );
+ }
$post = \wp_insert_post(
array(
diff --git a/tests/test-class-activitypub-mention.php b/tests/test-class-activitypub-mention.php
index 6f6b9ff..ca7395f 100644
--- a/tests/test-class-activitypub-mention.php
+++ b/tests/test-class-activitypub-mention.php
@@ -31,6 +31,7 @@ ENDPRE;
array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ),
array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ),
array( 'hallo @pfefferle@notiz.blog test', 'hallo @pfefferle@notiz.blog test' ),
+ array( 'hallo test', 'hallo test' ),
array( $code, $code ),
array( $pre, $pre ),
);
diff --git a/tests/test-class-db-activitypub-followers.php b/tests/test-class-db-activitypub-followers.php
index c502bf2..640c4d1 100644
--- a/tests/test-class-db-activitypub-followers.php
+++ b/tests/test-class-db-activitypub-followers.php
@@ -1,15 +1,60 @@
'Person',
- 'id' => 'http://sally.example.org',
- 'name' => 'Sally Smith',
- );
- \update_user_meta( 1, 'activitypub_followers', $followers );
+ public static $users = array(
+ 'username@example.org' => array(
+ 'url' => 'https://example.org/users/username',
+ 'inbox' => 'https://example.org/users/username/inbox',
+ 'name' => 'username',
+ 'prefferedUsername' => 'username',
+ ),
+ 'jon@example.com' => array(
+ 'url' => 'https://example.com/author/jon',
+ 'inbox' => 'https://example.com/author/jon/inbox',
+ 'name' => 'jon',
+ 'prefferedUsername' => 'jon',
+ ),
+ 'doe@example.org' => array(
+ 'url' => 'https://example.org/author/doe',
+ 'inbox' => 'https://example.org/author/doe/inbox',
+ 'name' => 'doe',
+ 'prefferedUsername' => 'doe',
+ ),
+ 'sally@example.org' => array(
+ 'url' => 'http://sally.example.org',
+ 'inbox' => 'http://sally.example.org/inbox',
+ 'name' => 'jon',
+ 'prefferedUsername' => 'jon',
+ ),
+ '12345@example.com' => array(
+ 'url' => 'https://12345.example.com',
+ 'inbox' => 'https://12345.example.com/inbox',
+ 'name' => '12345',
+ 'prefferedUsername' => '12345',
+ ),
+ );
- $db_followers = \Activitypub\Peer\Followers::get_followers( 1 );
+ public function set_up() {
+ parent::set_up();
+ add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
+ _delete_all_posts();
+ }
+
+ public function tear_down() {
+ remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );
+ parent::tear_down();
+ }
+
+ public function test_get_followers() {
+ $followers = array( 'https://example.com/author/jon', 'https://example.org/author/doe', 'http://sally.example.org' );
+
+ $pre_http_request = new MockAction();
+ add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
+
+ foreach ( $followers as $follower ) {
+ $response = \Activitypub\Collection\Followers::add_follower( 1, $follower );
+ }
+
+ $db_followers = \Activitypub\Collection\Followers::get_followers( 1 );
$this->assertEquals( 3, \count( $db_followers ) );
@@ -17,11 +62,72 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
}
public function test_add_follower() {
- $follower = 'https://example.com/author/' . \time();
- \Activitypub\Peer\Followers::add_follower( $follower, 1 );
+ $pre_http_request = new MockAction();
+ add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
- $db_followers = \Activitypub\Peer\Followers::get_followers( 1 );
+ $follower = 'https://12345.example.com';
+ \Activitypub\Collection\Followers::add_follower( 1, $follower );
+
+ $db_followers = \Activitypub\Collection\Followers::get_followers( 1 );
$this->assertContains( $follower, $db_followers );
}
+
+ public function test_get_follower() {
+ $followers = array( 'https://example.com/author/jon' );
+
+ $pre_http_request = new MockAction();
+ add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
+
+ foreach ( $followers as $follower ) {
+ \Activitypub\Collection\Followers::add_follower( 1, $follower );
+ }
+
+ $follower = \Activitypub\Collection\Followers::get_follower( 1, 'https://example.com/author/jon' );
+ $this->assertEquals( 'https://example.com/author/jon', $follower->get_actor() );
+
+ $follower = \Activitypub\Collection\Followers::get_follower( 1, 'http://sally.example.org' );
+ $this->assertNull( $follower );
+ }
+
+ public static function http_request_host_is_external( $in, $host ) {
+ if ( in_array( $host, array( 'example.com', 'example.org' ), true ) ) {
+ return true;
+ }
+ return $in;
+ }
+ public static function http_request_args( $args, $url ) {
+ if ( in_array( wp_parse_url( $url, PHP_URL_HOST ), array( 'example.com', 'example.org' ), true ) ) {
+ $args['reject_unsafe_urls'] = false;
+ }
+ return $args;
+ }
+
+ public static function pre_http_request( $preempt, $request, $url ) {
+ return array(
+ 'headers' => array(
+ 'content-type' => 'text/json',
+ ),
+ 'body' => '',
+ 'response' => array(
+ 'code' => 202,
+ ),
+ );
+ }
+
+ public static function http_response( $response, $args, $url ) {
+ return $response;
+ }
+
+ public static function pre_get_remote_metadata_by_actor( $pre, $actor ) {
+ if ( isset( self::$users[ $actor ] ) ) {
+ return self::$users[ $actor ];
+ }
+ foreach ( self::$users as $username => $data ) {
+ if ( $data['url'] === $actor ) {
+ return $data;
+ }
+ }
+ return $pre;
+ }
}
+
+
+
+
+
+
+ or
+
+
+
+
+
+
+
+
+
+
+
+
+