',
\__( 'Enhance your WordPress site’s performance and mitigate potential heavy loads caused by plugins like ActivityPub by setting up a system cron job to run WP Cron. This ensures scheduled tasks are executed consistently and reduces the reliance on website traffic for trigger events.', 'activitypub' )
);
- $result['actions'] .= sprintf(
+ $result['actions'] .= sprintf(
'
' . \__( 'For more information please visit fediverse.party', 'activitypub' ) . '
' .
'
' . \__( 'ActivityPub', 'activitypub' ) . '
' .
'
' . \__( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '
' .
'
' . \__( 'WebFinger', 'activitypub' ) . '
' .
'
' . \__( 'WebFinger is used to discover information about people or other entities on the Internet that are identified by a URI using standard Hypertext Transfer Protocol (HTTP) methods over a secure transport. A WebFinger resource returns a JavaScript Object Notation (JSON) object describing the entity that is queried. The JSON object is referred to as the JSON Resource Descriptor (JRD).', 'activitypub' ) . '
' .
'
' . \__( 'For a person, the type of information that might be discoverable via WebFinger includes a personal profile address, identity service, telephone number, or preferred avatar. For other entities on the Internet, a WebFinger resource might return JRDs containing link relations that enable a client to discover, for example, that a printer can print in color on A4 paper, the physical location of a server, or other static information.', 'activitypub' ) . '
' .
'
' . \__( 'On Mastodon [and other Plattforms], user profiles can be hosted either locally on the same website as yours, or remotely on a completely different website. The same username may be used on a different domain. Therefore, a Mastodon user\'s full mention consists of both the username and the domain, in the form @username@domain. In practical terms, @user@example.com is not the same as @user@example.org. If the domain is not included, Mastodon will try to find a local user named @username. However, in order to deliver to someone over ActivityPub, the @username@domain mention is not enough – mentions must be translated to an HTTPS URI first, so that the remote actor\'s inbox and outbox can be found. (This paragraph is copied from the Mastodon Documentation)', 'activitypub' ) . '
' . \__( 'For more information please visit webfinger.net', 'activitypub' ) . '
' .
'
' . \__( 'NodeInfo', 'activitypub' ) . '
' .
'
' . \__( 'NodeInfo is an effort to create a standardized way of exposing metadata about a server running one of the distributed social networks. The two key goals are being able to get better insights into the user base of distributed social networking and the ability to build tools that allow users to choose the best fitting software and server for their needs.', 'activitypub' ) . '
',
)
);
diff --git a/wp-content/plugins/activitypub/includes/model/class-application.php b/wp-content/plugins/activitypub/includes/model/class-application.php
index 35c57d64..b7d38af2 100644
--- a/wp-content/plugins/activitypub/includes/model/class-application.php
+++ b/wp-content/plugins/activitypub/includes/model/class-application.php
@@ -1,4 +1,10 @@
+ * @var string
*/
protected $webfinger;
+ /**
+ * Returns the type of the object.
+ *
+ * @return string The type of the object.
+ */
public function get_type() {
return 'Application';
}
+ /**
+ * Returns whether the Application manually approves followers.
+ *
+ * @return true Whether the Application manually approves followers.
+ */
public function get_manually_approves_followers() {
return true;
}
+ /**
+ * Returns the ID of the Application.
+ *
+ * @return string The ID of the Application.
+ */
public function get_id() {
return get_rest_url_by_path( 'application' );
}
@@ -73,24 +97,34 @@ class Application extends Actor {
return $this->get_url();
}
+ /**
+ * Get the Username.
+ *
+ * @return string The Username.
+ */
public function get_name() {
return 'application';
}
+ /**
+ * Get the preferred username.
+ *
+ * @return string The preferred username.
+ */
public function get_preferred_username() {
return $this->get_name();
}
- /**
+ /**
* Get the User-Icon.
*
* @return array The User-Icon.
*/
public function get_icon() {
- // try site icon first
+ // Try site icon first.
$icon_id = get_option( 'site_icon' );
- // try custom logo second
+ // Try custom logo second.
if ( ! $icon_id ) {
$icon_id = get_theme_mod( 'custom_logo' );
}
@@ -105,7 +139,7 @@ class Application extends Actor {
}
if ( ! $icon_url ) {
- // fallback to default icon
+ // Fallback to default icon.
$icon_url = plugins_url( '/assets/img/wp-logo.png', ACTIVITYPUB_PLUGIN_FILE );
}
@@ -131,6 +165,11 @@ class Application extends Actor {
return null;
}
+ /**
+ * Get the first published date.
+ *
+ * @return string The published date.
+ */
public function get_published() {
$first_post = new WP_Query(
array(
@@ -176,18 +215,23 @@ class Application extends Actor {
return $this->get_preferred_username() . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
}
+ /**
+ * Returns the public key.
+ *
+ * @return array The public key.
+ */
public function get_public_key() {
return array(
- 'id' => $this->get_id() . '#main-key',
- 'owner' => $this->get_id(),
+ 'id' => $this->get_id() . '#main-key',
+ 'owner' => $this->get_id(),
'publicKeyPem' => Signature::get_public_key_for( Users::APPLICATION_USER_ID ),
);
}
/**
- * Get the User-Description.
+ * Get the User description.
*
- * @return string The User-Description.
+ * @return string The User description.
*/
public function get_summary() {
return \wpautop(
@@ -198,6 +242,11 @@ class Application extends Actor {
);
}
+ /**
+ * Returns the canonical URL of the object.
+ *
+ * @return string|null The canonical URL of the object.
+ */
public function get_canonical_url() {
return \home_url();
}
diff --git a/wp-content/plugins/activitypub/includes/model/class-blog.php b/wp-content/plugins/activitypub/includes/model/class-blog.php
index 3c52abff..3cbd6f91 100644
--- a/wp-content/plugins/activitypub/includes/model/class-blog.php
+++ b/wp-content/plugins/activitypub/includes/model/class-blog.php
@@ -1,17 +1,27 @@
+ * @var string
*/
protected $webfinger;
/**
- * If the User is discoverable.
+ * Whether the User is discoverable.
*
* @see https://docs.joinmastodon.org/spec/activitypub/#discoverable
*
@@ -71,7 +81,7 @@ class Blog extends Actor {
protected $discoverable;
/**
- * Restrict posting to mods
+ * Restrict posting to mods.
*
* @see https://join-lemmy.org/docs/contributors/05-federation.html
*
@@ -79,18 +89,28 @@ class Blog extends Actor {
*/
protected $posting_restricted_to_mods;
+ /**
+ * Whether the User manually approves followers.
+ *
+ * @return false
+ */
public function get_manually_approves_followers() {
return false;
}
+ /**
+ * Whether the User is discoverable.
+ *
+ * @return boolean
+ */
public function get_discoverable() {
return true;
}
/**
- * Get the User-ID.
+ * Get the User ID.
*
- * @return string The User-ID.
+ * @return string The User ID.
*/
public function get_id() {
return $this->get_url();
@@ -112,9 +132,9 @@ class Blog extends Actor {
}
/**
- * Get the User-Name.
+ * Get the Username.
*
- * @return string The User-Name.
+ * @return string The Username.
*/
public function get_name() {
return \wp_strip_all_tags(
@@ -127,23 +147,29 @@ class Blog extends Actor {
}
/**
- * Get the User-Description.
+ * Get the User description.
*
- * @return string The User-Description.
+ * @return string The User description.
*/
public function get_summary() {
+ $summary = \get_option( 'activitypub_blog_description', null );
+
+ if ( ! $summary ) {
+ $summary = \get_bloginfo( 'description' );
+ }
+
return \wpautop(
\wp_kses(
- \get_bloginfo( 'description' ),
+ $summary,
'default'
)
);
}
/**
- * Get the User-Url.
+ * Get the User url.
*
- * @return string The User-Url.
+ * @return string The User url.
*/
public function get_url() {
return \esc_url( \trailingslashit( get_home_url() ) . '@' . $this->get_preferred_username() );
@@ -164,12 +190,12 @@ class Blog extends Actor {
* @return string The auto-generated Username.
*/
public static function get_default_username() {
- // check if domain host has a subdomain
+ // Check if domain host has a subdomain.
$host = \wp_parse_url( \get_home_url(), \PHP_URL_HOST );
$host = \preg_replace( '/^www\./i', '', $host );
/**
- * Filter the default blog username.
+ * Filters the default blog username.
*
* @param string $host The default username.
*/
@@ -177,12 +203,12 @@ class Blog extends Actor {
}
/**
- * Get the preferred User-Name.
+ * Get the preferred Username.
*
- * @return string The User-Name.
+ * @return string The Username.
*/
public function get_preferred_username() {
- $username = \get_option( 'activitypub_blog_user_identifier' );
+ $username = \get_option( 'activitypub_blog_identifier' );
if ( $username ) {
return $username;
@@ -192,15 +218,15 @@ class Blog extends Actor {
}
/**
- * Get the User-Icon.
+ * Get the User icon.
*
- * @return array The User-Icon.
+ * @return array The User icon.
*/
public function get_icon() {
- // try site icon first
+ // Try site_logo, falling back to site_icon, first.
$icon_id = get_option( 'site_icon' );
- // try custom logo second
+ // Try custom logo second.
if ( ! $icon_id ) {
$icon_id = get_theme_mod( 'custom_logo' );
}
@@ -215,7 +241,7 @@ class Blog extends Actor {
}
if ( ! $icon_url ) {
- // fallback to default icon
+ // Fallback to default icon.
$icon_url = plugins_url( '/assets/img/wp-logo.png', ACTIVITYPUB_PLUGIN_FILE );
}
@@ -231,16 +257,32 @@ class Blog extends Actor {
* @return array|null The User-Header-Image.
*/
public function get_image() {
- if ( \has_header_image() ) {
+ $header_image = get_option( 'activitypub_header_image' );
+ $image_url = null;
+
+ if ( $header_image ) {
+ $image_url = \wp_get_attachment_url( $header_image );
+ }
+
+ if ( ! $image_url && \has_header_image() ) {
+ $image_url = \get_header_image();
+ }
+
+ if ( $image_url ) {
return array(
'type' => 'Image',
- 'url' => esc_url( \get_header_image() ),
+ 'url' => esc_url( $image_url ),
);
}
return null;
}
+ /**
+ * Get the published date.
+ *
+ * @return string The published date.
+ */
public function get_published() {
$first_post = new WP_Query(
array(
@@ -259,10 +301,20 @@ class Blog extends Actor {
return \gmdate( 'Y-m-d\TH:i:s\Z', $time );
}
+ /**
+ * Get the canonical URL.
+ *
+ * @return string|null The canonical URL.
+ */
public function get_canonical_url() {
return \home_url();
}
+ /**
+ * Get the Moderators endpoint.
+ *
+ * @return string|null The Moderators endpoint.
+ */
public function get_moderators() {
if ( is_single_user() || 'Group' !== $this->get_type() ) {
return null;
@@ -271,6 +323,11 @@ class Blog extends Actor {
return get_rest_url_by_path( 'collections/moderators' );
}
+ /**
+ * Get attributedTo value.
+ *
+ * @return string|null The attributedTo value.
+ */
public function get_attributed_to() {
if ( is_single_user() || 'Group' !== $this->get_type() ) {
return null;
@@ -279,14 +336,24 @@ class Blog extends Actor {
return get_rest_url_by_path( 'collections/moderators' );
}
+ /**
+ * Get the public key information.
+ *
+ * @return array The public key.
+ */
public function get_public_key() {
return array(
- 'id' => $this->get_id() . '#main-key',
- 'owner' => $this->get_id(),
+ 'id' => $this->get_id() . '#main-key',
+ 'owner' => $this->get_id(),
'publicKeyPem' => Signature::get_public_key_for( $this->get__id() ),
);
}
+ /**
+ * Returns whether posting is restricted to mods.
+ *
+ * @return bool|null True if posting is restricted to mods, null if not applicable.
+ */
public function get_posting_restricted_to_mods() {
if ( 'Group' === $this->get_type() ) {
return true;
@@ -331,6 +398,11 @@ class Blog extends Actor {
return get_rest_url_by_path( sprintf( 'actors/%d/following', $this->get__id() ) );
}
+ /**
+ * Returns endpoints.
+ *
+ * @return array|null The endpoints.
+ */
public function get_endpoints() {
$endpoints = null;
@@ -361,45 +433,101 @@ class Blog extends Actor {
return get_rest_url_by_path( sprintf( 'actors/%d/collections/featured', $this->get__id() ) );
}
+ /**
+ * Returns whether the site is indexable.
+ *
+ * @return bool Whether the site is indexable.
+ */
public function get_indexable() {
- if ( \get_option( 'blog_public', 1 ) ) {
+ if ( is_blog_public() ) {
return true;
} else {
return false;
}
}
+ /**
+ * Update the Username.
+ *
+ * @param mixed $value The new value.
+ * @return bool True if the attribute was updated, false otherwise.
+ */
+ public function update_name( $value ) {
+ return \update_option( 'blogname', $value );
+ }
+
+ /**
+ * Update the User description.
+ *
+ * @param mixed $value The new value.
+ * @return bool True if the attribute was updated, false otherwise.
+ */
+ public function update_summary( $value ) {
+ return \update_option( 'blogdescription', $value );
+ }
+
+ /**
+ * Update the User icon.
+ *
+ * @param mixed $value The new value.
+ * @return bool True if the attribute was updated, false otherwise.
+ */
+ public function update_icon( $value ) {
+ if ( ! wp_attachment_is_image( $value ) ) {
+ return false;
+ }
+ return \update_option( 'site_icon', $value );
+ }
+
+ /**
+ * Update the User-Header-Image.
+ *
+ * @param mixed $value The new value.
+ * @return bool True if the attribute was updated, false otherwise.
+ */
+ public function update_header( $value ) {
+ if ( ! wp_attachment_is_image( $value ) ) {
+ return false;
+ }
+ return \update_option( 'activitypub_header_image', $value );
+ }
+
+ /**
+ * Get the User - Hashtags.
+ *
+ * @see https://docs.joinmastodon.org/spec/activitypub/#Hashtag
+ *
+ * @return array The User - Hashtags.
+ */
+ public function get_tag() {
+ $hashtags = array();
+
+ $args = array(
+ 'orderby' => 'count',
+ 'order' => 'DESC',
+ 'number' => 10,
+ );
+
+ $tags = get_tags( $args );
+
+ foreach ( $tags as $tag ) {
+ $hashtags[] = array(
+ 'type' => 'Hashtag',
+ 'href' => \get_tag_link( $tag->term_id ),
+ 'name' => esc_hashtag( $tag->name ),
+ );
+ }
+
+ return $hashtags;
+ }
+
/**
* Extend the User-Output with Attachments.
*
* @return array The extended User-Output.
*/
public function get_attachment() {
- $array = array();
-
- $array[] = array(
- 'type' => 'PropertyValue',
- 'name' => \__( 'Blog', 'activitypub' ),
- 'value' => \html_entity_decode(
- sprintf(
- '%s',
- \esc_attr( \home_url( '/' ) ),
- \esc_url( \home_url( '/' ) ),
- \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST )
- ),
- \ENT_QUOTES,
- 'UTF-8'
- ),
- );
-
- // Add support for FEP-fb2a, for more information see FEDERATION.md
- $array[] = array(
- 'type' => 'Link',
- 'name' => \__( 'Blog', 'activitypub' ),
- 'href' => \esc_url( \home_url( '/' ) ),
- 'rel' => array( 'me' ),
- );
-
- return $array;
+ $extra_fields = Extra_Fields::get_actor_fields( $this->_id );
+ return Extra_Fields::fields_to_attachments( $extra_fields );
}
}
diff --git a/wp-content/plugins/activitypub/includes/model/class-follower.php b/wp-content/plugins/activitypub/includes/model/class-follower.php
index 4590ea49..45ef6157 100644
--- a/wp-content/plugins/activitypub/includes/model/class-follower.php
+++ b/wp-content/plugins/activitypub/includes/model/class-follower.php
@@ -1,13 +1,18 @@
_id, 'activitypub_errors' );
+ return get_post_meta( $this->_id, 'activitypub_errors', false );
}
/**
* Get the Summary.
*
- * @return int The Summary.
+ * @return string The Summary.
*/
public function get_summary() {
if ( isset( $this->summary ) ) {
@@ -51,7 +56,7 @@ class Follower extends Actor {
* Getter for URL attribute.
*
* Falls back to ID, if no URL is set. This is relevant for
- * Plattforms like Lemmy, where the ID is the URL.
+ * Platforms like Lemmy, where the ID is the URL.
*
* @return string The URL.
*/
@@ -65,8 +70,6 @@ class Follower extends Actor {
/**
* Reset (delete) all errors.
- *
- * @return void
*/
public function reset_errors() {
delete_post_meta( $this->_id, 'activitypub_errors' );
@@ -103,21 +106,19 @@ class Follower extends Actor {
}
/**
- * Update the current Follower-Object.
- *
- * @return void
+ * Update the current Follower object.
*/
public function update() {
$this->save();
}
/**
- * Validate the current Follower-Object.
+ * Validate the current Follower object.
*
* @return boolean True if the verification was successful.
*/
public function is_valid() {
- // the minimum required attributes
+ // The minimum required attributes.
$required_attributes = array(
'id',
'preferredUsername',
@@ -136,9 +137,9 @@ class Follower extends Actor {
}
/**
- * Save the current Follower-Object.
+ * Save the current Follower object.
*
- * @return int|WP_Error The Post-ID or an WP_Error.
+ * @return int|WP_Error The post ID or an WP_Error.
*/
public function save() {
if ( ! $this->is_valid() ) {
@@ -148,7 +149,7 @@ class Follower extends Actor {
if ( ! $this->get__id() ) {
global $wpdb;
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$post_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
@@ -177,37 +178,35 @@ class Follower extends Actor {
);
if ( ! empty( $post_id ) ) {
- // If this is an update, prevent the "followed" date from being
- // overwritten by the current date.
+ // If this is an update, prevent the "followed" date from being overwritten by the current date.
$post = get_post( $post_id );
$args['post_date'] = $post->post_date;
$args['post_date_gmt'] = $post->post_date_gmt;
}
- $post_id = wp_insert_post( $args );
+ $post_id = wp_insert_post( $args );
$this->_id = $post_id;
return $post_id;
}
/**
- * Upsert the current Follower-Object.
+ * Upsert the current Follower object.
*
- * @return int|WP_Error The Post-ID or an WP_Error.
+ * @return int|WP_Error The post ID or an WP_Error.
*/
public function upsert() {
return $this->save();
}
/**
- * Delete the current Follower-Object.
+ * Delete the current Follower object.
*
* Beware that this os deleting a Follower for ALL users!!!
*
* To delete only the User connection (unfollow)
- * @see \Activitypub\Rest\Followers::remove_follower()
*
- * @return void
+ * @see \Activitypub\Rest\Followers::remove_follower()
*/
public function delete() {
wp_delete_post( $this->_id );
@@ -215,12 +214,10 @@ class Follower extends Actor {
/**
* Update the post meta.
- *
- * @return void
*/
protected function get_post_meta_input() {
- $meta_input = array();
- $meta_input['activitypub_inbox'] = $this->get_shared_inbox();
+ $meta_input = array();
+ $meta_input['activitypub_inbox'] = $this->get_shared_inbox();
$meta_input['activitypub_actor_json'] = $this->to_json();
return $meta_input;
@@ -239,9 +236,9 @@ class Follower extends Actor {
}
return array(
- 'type' => 'Image',
+ 'type' => 'Image',
'mediaType' => 'image/jpeg',
- 'url' => ACTIVITYPUB_PLUGIN_URL . 'assets/img/mp.jpg',
+ 'url' => ACTIVITYPUB_PLUGIN_URL . 'assets/img/mp.jpg',
);
}
@@ -278,7 +275,7 @@ class Follower extends Actor {
}
/**
- * Get the Icon URL (Avatar)
+ * Get the Icon URL (Avatar).
*
* @return string The URL to the Avatar.
*/
@@ -297,7 +294,7 @@ class Follower extends Actor {
}
/**
- * Get the Icon URL (Avatar)
+ * Get the Icon URL (Avatar).
*
* @return string The URL to the Avatar.
*/
@@ -333,13 +330,12 @@ class Follower extends Actor {
/**
* Convert a Custom-Post-Type input to an Activitypub\Model\Follower.
*
- * @return string The JSON string.
- *
- * @return array Activitypub\Model\Follower
+ * @param \WP_Post $post The post object.
+ * @return \Activitypub\Activity\Base_Object|WP_Error
*/
public static function init_from_cpt( $post ) {
$actor_json = get_post_meta( $post->ID, 'activitypub_actor_json', true );
- $object = self::init_from_json( $actor_json );
+ $object = self::init_from_json( $actor_json );
$object->set__id( $post->ID );
$object->set_id( $post->guid );
$object->set_name( $post->post_title );
@@ -370,10 +366,10 @@ class Follower extends Actor {
if ( $path ) {
if ( \strpos( $name, '@' ) !== false ) {
- // expected: https://example.com/@user (default URL pattern)
+ // Expected: https://example.com/@user (default URL pattern).
$name = \preg_replace( '|^/@?|', '', $path );
} else {
- // expected: https://example.com/users/user (default ID pattern)
+ // Expected: https://example.com/users/user (default ID pattern).
$parts = \explode( '/', $path );
$name = \array_pop( $parts );
}
@@ -383,7 +379,7 @@ class Follower extends Actor {
\strpos( $name, 'acct' ) === 0 ||
\strpos( $name, '@' ) === 0
) {
- // expected: user@example.com or acct:user@example (WebFinger)
+ // Expected: user@example.com or acct:user@example (WebFinger).
$name = \ltrim( $name, '@' );
$name = \ltrim( $name, 'acct:' );
$parts = \explode( '@', $name );
diff --git a/wp-content/plugins/activitypub/includes/model/class-post.php b/wp-content/plugins/activitypub/includes/model/class-post.php
deleted file mode 100644
index a4229539..00000000
--- a/wp-content/plugins/activitypub/includes/model/class-post.php
+++ /dev/null
@@ -1,136 +0,0 @@
-post = $post;
- $this->object = $transformer->to_object();
- }
- }
-
- /**
- * Returns the User ID.
- *
- * @return int the User ID.
- */
- public function get_user_id() {
- return apply_filters( 'activitypub_post_user_id', $this->post->post_author, $this->post );
- }
-
- /**
- * Converts this Object into an Array.
- *
- * @return array the array representation of a Post.
- */
- public function to_array() {
- return \apply_filters( 'activitypub_post', $this->object->to_array(), $this->post );
- }
-
- /**
- * Returns the Actor of this Object.
- *
- * @return string The URL of the Actor.
- */
- public function get_actor() {
- $user = Users::get_by_id( $this->get_user_id() );
-
- return $user->get_url();
- }
-
- /**
- * Converts this Object into a JSON String
- *
- * @return string
- */
- public function to_json() {
- return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
- }
-
- /**
- * Returns the URL of an Activity Object
- *
- * @return string
- */
- public function get_url() {
- return $this->object->get_url();
- }
-
- /**
- * Returns the ID of an Activity Object
- *
- * @return string
- */
- public function get_id() {
- return $this->object->get_id();
- }
-
- /**
- * Returns a list of Image Attachments
- *
- * @return array
- */
- public function get_attachments() {
- return $this->object->get_attachment();
- }
-
- /**
- * Returns a list of Tags, used in the Post
- *
- * @return array
- */
- public function get_tags() {
- return $this->object->get_tag();
- }
-
- /**
- * Returns the as2 object-type for a given post
- *
- * @return string the object-type
- */
- public function get_object_type() {
- return $this->object->get_type();
- }
-
- /**
- * Returns the content for the ActivityPub Item.
- *
- * @return string the content
- */
- public function get_content() {
- return $this->object->get_content();
- }
-}
diff --git a/wp-content/plugins/activitypub/includes/model/class-user.php b/wp-content/plugins/activitypub/includes/model/class-user.php
index dc6cb6c0..997c5210 100644
--- a/wp-content/plugins/activitypub/includes/model/class-user.php
+++ b/wp-content/plugins/activitypub/includes/model/class-user.php
@@ -1,18 +1,24 @@
+ * @var string
*/
protected $webfinger;
+ /**
+ * The type of the object.
+ *
+ * @return string The type of the object.
+ */
public function get_type() {
return 'Person';
}
+ /**
+ * Generate a User object from a WP_User.
+ *
+ * @param int $user_id The user ID.
+ *
+ * @return WP_Error|User The User object or WP_Error if user not found.
+ */
public static function from_wp_user( $user_id ) {
if ( is_user_disabled( $user_id ) ) {
return new WP_Error(
@@ -75,37 +93,37 @@ class User extends Actor {
);
}
- $object = new static();
+ $object = new static();
$object->_id = $user_id;
return $object;
}
/**
- * Get the User-ID.
+ * Get the user ID.
*
- * @return string The User-ID.
+ * @return string The user ID.
*/
public function get_id() {
return $this->get_url();
}
/**
- * Get the User-Name.
+ * Get the Username.
*
- * @return string The User-Name.
+ * @return string The Username.
*/
public function get_name() {
return \esc_attr( \get_the_author_meta( 'display_name', $this->_id ) );
}
/**
- * Get the User-Description.
+ * Get the User description.
*
- * @return string The User-Description.
+ * @return string The User description.
*/
public function get_summary() {
- $description = get_user_meta( $this->_id, 'activitypub_user_description', true );
+ $description = get_user_option( 'activitypub_description', $this->_id );
if ( empty( $description ) ) {
$description = get_user_meta( $this->_id, 'description', true );
}
@@ -113,28 +131,46 @@ class User extends Actor {
}
/**
- * Get the User-Url.
+ * Get the User url.
*
- * @return string The User-Url.
+ * @return string The User url.
*/
public function get_url() {
return \esc_url( \get_author_posts_url( $this->_id ) );
}
/**
- * Returns the User-URL with @-Prefix for the username.
+ * Returns the User URL with @-Prefix for the username.
*
- * @return string The User-URL with @-Prefix for the username.
+ * @return string The User URL with @-Prefix for the username.
*/
public function get_alternate_url() {
return \esc_url( \trailingslashit( get_home_url() ) . '@' . $this->get_preferred_username() );
}
+ /**
+ * Get the preferred username.
+ *
+ * @return string The preferred username.
+ */
public function get_preferred_username() {
return \esc_attr( \get_the_author_meta( 'login', $this->_id ) );
}
+ /**
+ * Get the User icon.
+ *
+ * @return array The User icon.
+ */
public function get_icon() {
+ $icon = \get_user_option( 'activitypub_icon', $this->_id );
+ if ( wp_attachment_is_image( $icon ) ) {
+ return array(
+ 'type' => 'Image',
+ 'url' => esc_url( wp_get_attachment_url( $icon ) ),
+ );
+ }
+
$icon = \esc_url(
\get_avatar_url(
$this->_id,
@@ -148,26 +184,51 @@ class User extends Actor {
);
}
+ /**
+ * Returns the header image.
+ *
+ * @return array|null The header image.
+ */
public function get_image() {
- if ( \has_header_image() ) {
- $image = \esc_url( \get_header_image() );
+ $header_image = get_user_option( 'activitypub_header_image', $this->_id );
+ $image_url = null;
+
+ if ( ! $header_image && \has_header_image() ) {
+ $image_url = \get_header_image();
+ }
+
+ if ( $header_image ) {
+ $image_url = \wp_get_attachment_url( $header_image );
+ }
+
+ if ( $image_url ) {
return array(
'type' => 'Image',
- 'url' => $image,
+ 'url' => esc_url( $image_url ),
);
}
return null;
}
+ /**
+ * Returns the date the user was created.
+ *
+ * @return false|string The date the user was created.
+ */
public function get_published() {
return \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( \get_the_author_meta( 'registered', $this->_id ) ) );
}
+ /**
+ * Returns the public key.
+ *
+ * @return array The public key.
+ */
public function get_public_key() {
return array(
- 'id' => $this->get_id() . '#main-key',
- 'owner' => $this->get_id(),
+ 'id' => $this->get_id() . '#main-key',
+ 'owner' => $this->get_id(),
'publicKeyPem' => Signature::get_public_key_for( $this->get__id() ),
);
}
@@ -217,6 +278,11 @@ class User extends Actor {
return get_rest_url_by_path( sprintf( 'actors/%d/collections/featured', $this->get__id() ) );
}
+ /**
+ * Returns the endpoints.
+ *
+ * @return array|null The endpoints.
+ */
public function get_endpoints() {
$endpoints = null;
@@ -235,74 +301,8 @@ class User extends Actor {
* @return array The extended User-Output.
*/
public function get_attachment() {
- $extra_fields = get_actor_extra_fields( $this->_id );
-
- $attachments = array();
-
- foreach ( $extra_fields as $post ) {
- $content = \get_the_content( null, false, $post );
- $content = \make_clickable( $content );
- $content = \do_blocks( $content );
- $content = \wptexturize( $content );
- $content = \wp_filter_content_tags( $content );
- // replace script and style elements
- $content = \preg_replace( '@<(script|style)[^>]*?>.*?\\1>@si', '', $content );
- $content = \strip_shortcodes( $content );
- $content = \trim( \preg_replace( '/[\n\r\t]/', '', $content ) );
-
- $attachments[] = array(
- 'type' => 'PropertyValue',
- 'name' => \get_the_title( $post ),
- 'value' => \html_entity_decode(
- $content,
- \ENT_QUOTES,
- 'UTF-8'
- ),
- );
-
- $link_added = false;
-
- // Add support for FEP-fb2a, for more information see FEDERATION.md
- if ( \class_exists( '\WP_HTML_Tag_Processor' ) ) {
- $tags = new \WP_HTML_Tag_Processor( $content );
- $tags->next_tag();
-
- if ( 'P' === $tags->get_tag() ) {
- $tags->next_tag();
- }
-
- if ( 'A' === $tags->get_tag() ) {
- $tags->set_bookmark( 'link' );
- if ( ! $tags->next_tag() ) {
- $tags->seek( 'link' );
- $attachment = array(
- 'type' => 'Link',
- 'name' => \get_the_title( $post ),
- 'href' => \esc_url( $tags->get_attribute( 'href' ) ),
- 'rel' => explode( ' ', $tags->get_attribute( 'rel' ) ),
- );
-
- $link_added = true;
- }
- }
- }
-
- if ( ! $link_added ) {
- $attachment = array(
- 'type' => 'Note',
- 'name' => \get_the_title( $post ),
- 'content' => \html_entity_decode(
- $content,
- \ENT_QUOTES,
- 'UTF-8'
- ),
- );
- }
-
- $attachments[] = $attachment;
- }
-
- return $attachments;
+ $extra_fields = Extra_Fields::get_actor_fields( $this->_id );
+ return Extra_Fields::fields_to_attachments( $extra_fields );
}
/**
@@ -314,23 +314,93 @@ class User extends Actor {
return $this->get_preferred_username() . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
}
+ /**
+ * Returns the canonical URL.
+ *
+ * @return string The canonical URL.
+ */
public function get_canonical_url() {
return $this->get_url();
}
+ /**
+ * Returns the streams.
+ *
+ * @return null The streams.
+ */
public function get_streams() {
return null;
}
+ /**
+ * Returns the tag.
+ *
+ * @return array The tag.
+ */
public function get_tag() {
return array();
}
+ /**
+ * Returns the indexable state.
+ *
+ * @return bool Whether the user is indexable.
+ */
public function get_indexable() {
- if ( \get_option( 'blog_public', 1 ) ) {
+ if ( is_blog_public() ) {
return true;
} else {
return false;
}
}
+
+ /**
+ * Update the username.
+ *
+ * @param string $value The new value.
+ * @return int|WP_Error The updated user ID or WP_Error on failure.
+ */
+ public function update_name( $value ) {
+ $userdata = array(
+ 'ID' => $this->_id,
+ 'display_name' => $value,
+ );
+ return \wp_update_user( $userdata );
+ }
+
+ /**
+ * Update the User description.
+ *
+ * @param string $value The new value.
+ * @return bool True if the attribute was updated, false otherwise.
+ */
+ public function update_summary( $value ) {
+ return \update_user_option( $this->_id, 'activitypub_description', $value );
+ }
+
+ /**
+ * Update the User icon.
+ *
+ * @param int $value The new value. Should be an attachment ID.
+ * @return bool True if the attribute was updated, false otherwise.
+ */
+ public function update_icon( $value ) {
+ if ( ! wp_attachment_is_image( $value ) ) {
+ return false;
+ }
+ return update_user_option( $this->_id, 'activitypub_icon', $value );
+ }
+
+ /**
+ * Update the User-Header-Image.
+ *
+ * @param int $value The new value. Should be an attachment ID.
+ * @return bool True if the attribute was updated, false otherwise.
+ */
+ public function update_header( $value ) {
+ if ( ! wp_attachment_is_image( $value ) ) {
+ return false;
+ }
+ return \update_user_option( $this->_id, 'activitypub_header_image', $value );
+ }
}
diff --git a/wp-content/plugins/activitypub/includes/rest/class-actors.php b/wp-content/plugins/activitypub/includes/rest/class-actors.php
index f5854a68..60f03d29 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-actors.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-actors.php
@@ -1,18 +1,22 @@
get_param( 'user_id' );
@@ -77,14 +81,17 @@ class Actors {
return $user;
}
- // redirect to canonical URL if it is not an ActivityPub request
+ $link_header = sprintf( '<%1$s>; rel="alternate"; type="application/activity+json"', $user->get_id() );
+
+ // Redirect to canonical URL if it is not an ActivityPub request.
if ( ! is_activitypub_request() ) {
+ header( 'Link: ' . $link_header );
header( 'Location: ' . $user->get_canonical_url(), true, 301 );
exit;
}
- /*
- * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ /**
+ * Action triggered prior to the ActivityPub profile being created and sent to the client.
*/
\do_action( 'activitypub_rest_users_pre' );
@@ -92,17 +99,18 @@ class Actors {
$rest_response = new WP_REST_Response( $json, 200 );
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
+ $rest_response->header( 'Link', $link_header );
return $rest_response;
}
/**
- * Endpoint for remote follow UI/Block
+ * Endpoint for remote follow UI/Block.
*
* @param WP_REST_Request $request The request object.
*
- * @return void|string The URL to the remote follow page
+ * @return WP_REST_Response|\WP_Error The response object or WP_Error.
*/
public static function remote_follow_get( WP_REST_Request $request ) {
$resource = $request->get_param( 'resource' );
@@ -123,15 +131,18 @@ class Actors {
$url = str_replace( '{uri}', $resource, $template );
return new WP_REST_Response(
- array( 'url' => $url, 'template' => $template ),
+ array(
+ 'url' => $url,
+ 'template' => $template,
+ ),
200
);
}
/**
- * The supported parameters
+ * The supported parameters.
*
- * @return array list of parameters
+ * @return array List of parameters,
*/
public static function request_parameters() {
$params = array();
diff --git a/wp-content/plugins/activitypub/includes/rest/class-collection.php b/wp-content/plugins/activitypub/includes/rest/class-collection.php
index 296789fb..02cc99bf 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-collection.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-collection.php
@@ -1,4 +1,10 @@
[\w\-\.]+)s/(?P[\w\-\.]+)/replies',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( self::class, 'replies_get' ),
+ 'args' => self::request_parameters_for_replies(),
+ 'permission_callback' => '__return_true',
+ ),
+ )
+ );
+ }
+
+ /**
+ * The endpoint for replies collections.
+ *
+ * @param \WP_REST_Request $request The request object.
+ *
+ * @return WP_REST_Response|\WP_Error The response object or WP_Error.
+ */
+ public static function replies_get( $request ) {
+ $type = $request->get_param( 'type' );
+
+ // Get the WordPress object of that "owns" the requested replies.
+ switch ( $type ) {
+ case 'comment':
+ $wp_object = \get_comment( $request->get_param( 'id' ) );
+ break;
+ case 'post':
+ default:
+ $wp_object = \get_post( $request->get_param( 'id' ) );
+ break;
+ }
+
+ if ( ! isset( $wp_object ) || is_wp_error( $wp_object ) ) {
+ return new WP_Error(
+ 'activitypub_replies_collection_does_not_exist',
+ \sprintf(
+ // translators: %s: The type (post, comment, etc.) for which no replies collection exists.
+ \__( 'No reply collection exists for the type %s.', 'activitypub' ),
+ $type
+ )
+ );
+ }
+
+ $page = intval( $request->get_param( 'page' ) );
+
+ // If the request parameter page is present get the CollectionPage otherwise the replies collection.
+ if ( isset( $page ) ) {
+ $response = Replies::get_collection_page( $wp_object, $page );
+ } else {
+ $response = Replies::get_collection( $wp_object );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ // Add ActivityPub Context.
+ $response = array_merge(
+ array( '@context' => Base_Object::JSON_LD_CONTEXT ),
+ $response
+ );
+
+ return new WP_REST_Response( $response, 200 );
}
/**
* The Featured Tags endpoint
*
- * @param WP_REST_Request $request The request object.
+ * @param \WP_REST_Request $request The request object.
*
- * @return WP_REST_Response The response object.
+ * @return WP_REST_Response|\WP_Error The response object or WP_Error.
*/
public static function tags_get( $request ) {
$user_id = $request->get_param( 'user_id' );
@@ -126,9 +202,9 @@ class Collection {
/**
* Featured posts endpoint
*
- * @param WP_REST_Request $request The request object.
+ * @param \WP_REST_Request $request The request object.
*
- * @return WP_REST_Response The response object.
+ * @return WP_REST_Response|\WP_Error The response object or WP_Error.
*/
public static function featured_get( $request ) {
$user_id = $request->get_param( 'user_id' );
@@ -184,13 +260,11 @@ class Collection {
}
/**
- * Moderators endpoint
- *
- * @param WP_REST_Request $request The request object.
+ * Moderators endpoint.
*
* @return WP_REST_Response The response object.
*/
- public static function moderators_get( $request ) {
+ public static function moderators_get() {
$response = array(
'@context' => Actor::JSON_LD_CONTEXT,
'id' => get_rest_url_by_path( 'collections/moderators' ),
@@ -211,16 +285,38 @@ class Collection {
}
/**
- * The supported parameters
+ * The supported parameters.
*
- * @return array list of parameters
+ * @return array List of parameters.
*/
public static function request_parameters() {
$params = array();
$params['user_id'] = array(
'required' => true,
- 'type' => 'string',
+ 'type' => 'string',
+ );
+
+ return $params;
+ }
+
+ /**
+ * The supported parameters.
+ *
+ * @return array list of parameters.
+ */
+ public static function request_parameters_for_replies() {
+ $params = array();
+
+ $params['type'] = array(
+ 'required' => true,
+ 'type' => 'string',
+ 'enum' => array( 'post', 'comment' ),
+ );
+
+ $params['id'] = array(
+ 'required' => true,
+ 'type' => 'string',
);
return $params;
diff --git a/wp-content/plugins/activitypub/includes/rest/class-comment.php b/wp-content/plugins/activitypub/includes/rest/class-comment.php
index a31b5978..095c8589 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-comment.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-comment.php
@@ -1,4 +1,10 @@
get_param( 'resource' );
@@ -75,20 +81,19 @@ class Comment {
return $template;
}
- $comment_meta = \get_comment_meta( $comment_id );
+ $resource = Comment_Utils::get_source_id( $comment_id );
- if ( ! empty( $comment_meta['source_id'][0] ) ) {
- $resource = $comment_meta['source_id'][0];
- } elseif ( ! empty( $comment_meta['source_url'][0] ) ) {
- $resource = $comment_meta['source_url'][0];
- } else {
+ if ( ! $resource ) {
$resource = Comment_Utils::generate_id( $comment );
}
$url = str_replace( '{uri}', $resource, $template );
return new WP_REST_Response(
- array( 'url' => $url, 'template' => $template ),
+ array(
+ 'url' => $url,
+ 'template' => $template,
+ ),
200
);
}
diff --git a/wp-content/plugins/activitypub/includes/rest/class-followers.php b/wp-content/plugins/activitypub/includes/rest/class-followers.php
index ca882cf3..fe97548e 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-followers.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-followers.php
@@ -1,7 +1,12 @@
get_param( 'user_id' );
@@ -64,36 +69,34 @@ class Followers {
$page = (int) $request->get_param( 'page' );
$context = $request->get_param( 'context' );
- /*
- * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ /**
+ * Action triggered prior to the ActivityPub profile being created and sent to the client
*/
\do_action( 'activitypub_rest_followers_pre' );
$data = Follower_Collection::get_followers_with_count( $user_id, $per_page, $page, array( 'order' => ucwords( $order ) ) );
$json = new stdClass();
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$json->{'@context'} = \Activitypub\get_context();
+ $json->id = get_rest_url_by_path( sprintf( 'actors/%d/followers', $user->get__id() ) );
+ $json->generator = 'http://wordpress.org/?v=' . get_masked_wp_version();
+ $json->actor = $user->get_id();
+ $json->type = 'OrderedCollectionPage';
+ $json->totalItems = $data['total'];
+ $json->partOf = get_rest_url_by_path( sprintf( 'actors/%d/followers', $user->get__id() ) );
- $json->id = get_rest_url_by_path( sprintf( 'actors/%d/followers', $user->get__id() ) );
- $json->generator = 'http://wordpress.org/?v=' . get_masked_wp_version();
- $json->actor = $user->get_id();
- $json->type = 'OrderedCollectionPage';
+ $json->first = \add_query_arg( 'page', 1, $json->partOf );
+ $json->last = \add_query_arg( 'page', \ceil( $json->totalItems / $per_page ), $json->partOf );
- $json->totalItems = $data['total']; // phpcs:ignore
- $json->partOf = get_rest_url_by_path( sprintf( 'actors/%d/followers', $user->get__id() ) ); // phpcs:ignore
-
- $json->first = \add_query_arg( 'page', 1, $json->partOf ); // phpcs:ignore
- $json->last = \add_query_arg( 'page', \ceil ( $json->totalItems / $per_page ), $json->partOf ); // phpcs:ignore
-
- if ( $page && ( ( \ceil ( $json->totalItems / $per_page ) ) > $page ) ) { // phpcs:ignore
- $json->next = \add_query_arg( 'page', $page + 1, $json->partOf ); // phpcs:ignore
+ if ( $page && ( ( \ceil( $json->totalItems / $per_page ) ) > $page ) ) {
+ $json->next = \add_query_arg( 'page', $page + 1, $json->partOf );
}
- if ( $page && ( $page > 1 ) ) { // phpcs:ignore
- $json->prev = \add_query_arg( 'page', $page - 1, $json->partOf ); // phpcs:ignore
+ if ( $page && ( $page > 1 ) ) {
+ $json->prev = \add_query_arg( 'page', $page - 1, $json->partOf );
}
- // phpcs:ignore
$json->orderedItems = array_map(
function ( $item ) use ( $context ) {
if ( 'full' === $context ) {
@@ -103,6 +106,7 @@ class Followers {
},
$data['followers']
);
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$rest_response = new WP_REST_Response( $json, 200 );
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
@@ -111,20 +115,20 @@ class Followers {
}
/**
- * The supported parameters
+ * The supported parameters.
*
- * @return array list of parameters
+ * @return array List of parameters.
*/
public static function request_parameters() {
$params = array();
$params['page'] = array(
- 'type' => 'integer',
+ 'type' => 'integer',
'default' => 1,
);
$params['per_page'] = array(
- 'type' => 'integer',
+ 'type' => 'integer',
'default' => 20,
);
@@ -136,13 +140,13 @@ class Followers {
$params['user_id'] = array(
'required' => true,
- 'type' => 'string',
+ 'type' => 'string',
);
$params['context'] = array(
- 'type' => 'string',
+ 'type' => 'string',
'default' => 'simple',
- 'enum' => array( 'simple', 'full' ),
+ 'enum' => array( 'simple', 'full' ),
);
return $params;
diff --git a/wp-content/plugins/activitypub/includes/rest/class-following.php b/wp-content/plugins/activitypub/includes/rest/class-following.php
index 4e077279..bf546e96 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-following.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-following.php
@@ -1,4 +1,10 @@
get_param( 'user_id' );
@@ -58,8 +64,8 @@ class Following {
return $user;
}
- /*
- * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ /**
+ * Action triggered prior to the ActivityPub profile being created and sent to the client.
*/
\do_action( 'activitypub_rest_following_pre' );
@@ -67,19 +73,25 @@ class Following {
$json->{'@context'} = \Activitypub\get_context();
- $json->id = get_rest_url_by_path( sprintf( 'actors/%d/following', $user->get__id() ) );
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ $json->id = get_rest_url_by_path( sprintf( 'actors/%d/following', $user->get__id() ) );
$json->generator = 'http://wordpress.org/?v=' . get_masked_wp_version();
- $json->actor = $user->get_id();
- $json->type = 'OrderedCollectionPage';
+ $json->actor = $user->get_id();
+ $json->type = 'OrderedCollectionPage';
+ $json->partOf = get_rest_url_by_path( sprintf( 'actors/%d/following', $user->get__id() ) );
- $json->partOf = get_rest_url_by_path( sprintf( 'actors/%d/following', $user->get__id() ) ); // phpcs:ignore
+ /**
+ * Filter the list of following urls.
+ *
+ * @param array $items The array of following urls.
+ * @param \Activitypub\Model\User $user The user object.
+ */
+ $items = apply_filters( 'activitypub_rest_following', array(), $user );
- $items = apply_filters( 'activitypub_rest_following', array(), $user ); // phpcs:ignore
-
- $json->totalItems = is_countable( $items ) ? count( $items ) : 0; // phpcs:ignore
- $json->orderedItems = $items; // phpcs:ignore
-
- $json->first = $json->partOf; // phpcs:ignore
+ $json->totalItems = is_countable( $items ) ? count( $items ) : 0;
+ $json->orderedItems = $items;
+ $json->first = $json->partOf;
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$rest_response = new WP_REST_Response( $json, 200 );
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
@@ -88,9 +100,9 @@ class Following {
}
/**
- * The supported parameters
+ * The supported parameters.
*
- * @return array list of parameters
+ * @return array List of parameters.
*/
public static function request_parameters() {
$params = array();
@@ -101,7 +113,7 @@ class Following {
$params['user_id'] = array(
'required' => true,
- 'type' => 'string',
+ 'type' => 'string',
);
return $params;
@@ -111,22 +123,22 @@ class Following {
* Add the Blog Authors to the following list of the Blog Actor
* if Blog not in single mode.
*
- * @param array $array The array of following urls.
- * @param User $user The user object.
+ * @param array $follow_list The array of following urls.
+ * @param \Activitypub\Model\User $user The user object.
*
* @return array The array of following urls.
*/
- public static function default_following( $array, $user ) {
+ public static function default_following( $follow_list, $user ) {
if ( 0 !== $user->get__id() || is_single_user() ) {
- return $array;
+ return $follow_list;
}
$users = User_Collection::get_collection();
foreach ( $users as $user ) {
- $array[] = $user->get_url();
+ $follow_list[] = $user->get_url();
}
- return $array;
+ return $follow_list;
}
}
diff --git a/wp-content/plugins/activitypub/includes/rest/class-inbox.php b/wp-content/plugins/activitypub/includes/rest/class-inbox.php
index 161fcf19..ea550295 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-inbox.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-inbox.php
@@ -1,21 +1,25 @@
get_param( 'user_id' );
@@ -80,29 +84,33 @@ class Inbox {
return $user;
}
- $page = $request->get_param( 'page', 0 );
-
- /*
- * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ /**
+ * Action triggered prior to the ActivityPub profile being created and sent to the client.
*/
\do_action( 'activitypub_rest_inbox_pre' );
$json = new \stdClass();
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$json->{'@context'} = get_context();
- $json->id = get_rest_url_by_path( sprintf( 'actors/%d/inbox', $user->get__id() ) );
- $json->generator = 'http://wordpress.org/?v=' . get_masked_wp_version();
- $json->type = 'OrderedCollectionPage';
- $json->partOf = get_rest_url_by_path( sprintf( 'actors/%d/inbox', $user->get__id() ) ); // phpcs:ignore
- $json->totalItems = 0; // phpcs:ignore
- $json->orderedItems = array(); // phpcs:ignore
- $json->first = $json->partOf; // phpcs:ignore
+ $json->id = get_rest_url_by_path( sprintf( 'actors/%d/inbox', $user->get__id() ) );
+ $json->generator = 'http://wordpress.org/?v=' . get_masked_wp_version();
+ $json->type = 'OrderedCollectionPage';
+ $json->partOf = get_rest_url_by_path( sprintf( 'actors/%d/inbox', $user->get__id() ) );
+ $json->totalItems = 0;
+ $json->orderedItems = array();
+ $json->first = $json->partOf;
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
- // filter output
+ /**
+ * Filter the ActivityPub inbox array.
+ *
+ * @param array $json The ActivityPub inbox array.
+ */
$json = \apply_filters( 'activitypub_rest_inbox_array', $json );
- /*
- * Action triggerd after the ActivityPub profile has been created and sent to the client
+ /**
+ * Action triggered after the ActivityPub profile has been created and sent to the client.
*/
\do_action( 'activitypub_inbox_post' );
@@ -113,11 +121,11 @@ class Inbox {
}
/**
- * Handles user-inbox requests
+ * Handles user-inbox requests.
*
- * @param WP_REST_Request $request
+ * @param \WP_REST_Request $request The request object.
*
- * @return WP_REST_Response
+ * @return WP_REST_Response|\WP_Error The response object or WP_Error.
*/
public static function user_inbox_post( $request ) {
$user_id = $request->get_param( 'user_id' );
@@ -132,7 +140,23 @@ class Inbox {
$type = $request->get_param( 'type' );
$type = \strtolower( $type );
+ /**
+ * ActivityPub inbox action.
+ *
+ * @param array $data The data array.
+ * @param int|null $user_id The user ID.
+ * @param string $type The type of the activity.
+ * @param Activity $activity The Activity object.
+ */
\do_action( 'activitypub_inbox', $data, $user->get__id(), $type, $activity );
+
+ /**
+ * ActivityPub inbox action for specific activity types.
+ *
+ * @param array $data The data array.
+ * @param int|null $user_id The user ID.
+ * @param Activity $activity The Activity object.
+ */
\do_action( "activitypub_inbox_{$type}", $data, $user->get__id(), $activity );
$rest_response = new WP_REST_Response( array(), 202 );
@@ -142,9 +166,9 @@ class Inbox {
}
/**
- * The shared inbox
+ * The shared inbox.
*
- * @param WP_REST_Request $request
+ * @param \WP_REST_Request $request The request object.
*
* @return WP_REST_Response
*/
@@ -154,7 +178,23 @@ class Inbox {
$type = $request->get_param( 'type' );
$type = \strtolower( $type );
+ /**
+ * ActivityPub inbox action.
+ *
+ * @param array $data The data array.
+ * @param int|null $user_id The user ID.
+ * @param string $type The type of the activity.
+ * @param Activity $activity The Activity object.
+ */
\do_action( 'activitypub_inbox', $data, null, $type, $activity );
+
+ /**
+ * ActivityPub inbox action for specific activity types.
+ *
+ * @param array $data The data array.
+ * @param int|null $user_id The user ID.
+ * @param Activity $activity The Activity object.
+ */
\do_action( "activitypub_inbox_{$type}", $data, null, $activity );
$rest_response = new WP_REST_Response( array(), 202 );
@@ -164,9 +204,9 @@ class Inbox {
}
/**
- * The supported parameters
+ * The supported parameters.
*
- * @return array list of parameters
+ * @return array List of parameters.
*/
public static function user_inbox_get_parameters() {
$params = array();
@@ -177,100 +217,68 @@ class Inbox {
$params['user_id'] = array(
'required' => true,
- 'type' => 'string',
+ 'type' => 'string',
);
return $params;
}
/**
- * The supported parameters
+ * The supported parameters.
*
- * @return array list of parameters
+ * @return array List of parameters.
*/
public static function user_inbox_post_parameters() {
$params = array();
- $params['page'] = array(
- 'type' => 'integer',
- );
-
$params['user_id'] = array(
'required' => true,
- 'type' => 'string',
+ 'type' => 'string',
);
$params['id'] = array(
- 'required' => true,
+ 'required' => true,
'sanitize_callback' => 'esc_url_raw',
);
$params['actor'] = array(
- 'required' => true,
- 'sanitize_callback' => function ( $param, $request, $key ) {
- return object_to_uri( $param );
- },
+ 'required' => true,
+ 'sanitize_callback' => '\Activitypub\object_to_uri',
);
$params['type'] = array(
'required' => true,
- //'type' => 'enum',
- //'enum' => array( 'Create' ),
- //'sanitize_callback' => function ( $param, $request, $key ) {
- // return \strtolower( $param );
- //},
);
$params['object'] = array(
- 'required' => true,
+ 'required' => true,
+ 'validate_callback' => function ( $param, $request, $key ) {
+ /**
+ * Filter the ActivityPub object validation.
+ *
+ * @param bool $validate The validation result.
+ * @param array $param The object data.
+ * @param object $request The request object.
+ * @param string $key The key.
+ */
+ return apply_filters( 'activitypub_validate_object', true, $param, $request, $key );
+ },
);
return $params;
}
/**
- * The supported parameters
+ * The supported parameters.
*
- * @return array list of parameters
+ * @return array List of parameters.
*/
public static function shared_inbox_post_parameters() {
- $params = array();
-
- $params['page'] = array(
- 'type' => 'integer',
- );
-
- $params['id'] = array(
- 'required' => true,
- 'type' => 'string',
- 'sanitize_callback' => 'esc_url_raw',
- );
-
- $params['actor'] = array(
- 'required' => true,
- //'type' => array( 'object', 'string' ),
- 'sanitize_callback' => function ( $param, $request, $key ) {
- return object_to_uri( $param );
- },
- );
-
- $params['type'] = array(
- 'required' => true,
- //'type' => 'enum',
- //'enum' => array( 'Create' ),
- //'sanitize_callback' => function ( $param, $request, $key ) {
- // return \strtolower( $param );
- //},
- );
-
- $params['object'] = array(
- 'required' => true,
- //'type' => 'object',
- );
+ $params = self::user_inbox_post_parameters();
$params['to'] = array(
- 'required' => false,
- 'sanitize_callback' => function ( $param, $request, $key ) {
+ 'required' => false,
+ 'sanitize_callback' => function ( $param ) {
if ( \is_string( $param ) ) {
$param = array( $param );
}
@@ -280,7 +288,7 @@ class Inbox {
);
$params['cc'] = array(
- 'sanitize_callback' => function ( $param, $request, $key ) {
+ 'sanitize_callback' => function ( $param ) {
if ( \is_string( $param ) ) {
$param = array( $param );
}
@@ -290,7 +298,7 @@ class Inbox {
);
$params['bcc'] = array(
- 'sanitize_callback' => function ( $param, $request, $key ) {
+ 'sanitize_callback' => function ( $param ) {
if ( \is_string( $param ) ) {
$param = array( $param );
}
@@ -303,15 +311,15 @@ class Inbox {
}
/**
- * Get local user recipients
+ * Get local user recipients.
*
- * @param array $data
+ * @param array $data The data array.
*
- * @return array The list of local users
+ * @return array The list of local users.
*/
public static function get_recipients( $data ) {
$recipients = extract_recipients_from_activity( $data );
- $users = array();
+ $users = array();
foreach ( $recipients as $recipient ) {
$user_id = url_to_authorid( $recipient );
diff --git a/wp-content/plugins/activitypub/includes/rest/class-interaction.php b/wp-content/plugins/activitypub/includes/rest/class-interaction.php
new file mode 100644
index 00000000..8bf78267
--- /dev/null
+++ b/wp-content/plugins/activitypub/includes/rest/class-interaction.php
@@ -0,0 +1,118 @@
+ \WP_REST_Server::READABLE,
+ 'callback' => array( self::class, 'get' ),
+ 'permission_callback' => '__return_true',
+ 'args' => array(
+ 'uri' => array(
+ 'type' => 'string',
+ 'required' => true,
+ 'sanitize_callback' => 'esc_url',
+ ),
+ ),
+ ),
+ )
+ );
+ }
+
+ /**
+ * Handle GET request.
+ *
+ * @param \WP_REST_Request $request The request object.
+ *
+ * @return WP_REST_Response Redirect to the editor or die.
+ */
+ public static function get( $request ) {
+ $uri = $request->get_param( 'uri' );
+ $redirect_url = null;
+ $object = Http::get_remote_object( $uri );
+
+ if (
+ \is_wp_error( $object ) ||
+ ! isset( $object['type'] )
+ ) {
+ \wp_die(
+ \esc_html__(
+ 'The URL is not supported!',
+ 'activitypub'
+ ),
+ 400
+ );
+ }
+
+ if ( ! empty( $object['url'] ) ) {
+ $uri = \esc_url( $object['url'] );
+ }
+
+ switch ( $object['type'] ) {
+ case 'Group':
+ case 'Person':
+ case 'Service':
+ case 'Application':
+ case 'Organization':
+ $redirect_url = \apply_filters( 'activitypub_interactions_follow_url', $redirect_url, $uri, $object );
+ break;
+ default:
+ $redirect_url = \admin_url( 'post-new.php?in_reply_to=' . $uri );
+ $redirect_url = \apply_filters( 'activitypub_interactions_reply_url', $redirect_url, $uri, $object );
+ }
+
+ /**
+ * Filter the redirect URL.
+ *
+ * @param string $redirect_url The URL to redirect to.
+ * @param string $uri The URI of the object.
+ * @param array $object The object.
+ */
+ $redirect_url = \apply_filters( 'activitypub_interactions_url', $redirect_url, $uri, $object );
+
+ // Check if hook is implemented.
+ if ( ! $redirect_url ) {
+ \wp_die(
+ esc_html__(
+ 'This Interaction type is not supported yet!',
+ 'activitypub'
+ ),
+ 400
+ );
+ }
+
+ return new WP_REST_Response(
+ null,
+ 302,
+ array(
+ 'Location' => \esc_url( $redirect_url ),
+ )
+ );
+ }
+}
diff --git a/wp-content/plugins/activitypub/includes/rest/class-nodeinfo.php b/wp-content/plugins/activitypub/includes/rest/class-nodeinfo.php
index 02b89b6c..3eb5fc00 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-nodeinfo.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-nodeinfo.php
@@ -1,4 +1,10 @@
'wordpress',
+ 'name' => 'wordpress',
'version' => get_masked_wp_version(),
);
- $posts = \wp_count_posts();
+ $posts = \wp_count_posts();
$comments = \wp_count_comments();
$nodeinfo['usage'] = array(
- 'users' => array(
+ 'users' => array(
'total' => get_total_users(),
'activeMonth' => get_active_users( '1 month ago' ),
'activeHalfyear' => get_active_users( '6 month ago' ),
),
- 'localPosts' => (int) $posts->publish,
+ 'localPosts' => (int) $posts->publish,
'localComments' => (int) $comments->approved,
);
$nodeinfo['openRegistrations'] = false;
- $nodeinfo['protocols'] = array( 'activitypub' );
+ $nodeinfo['protocols'] = array( 'activitypub' );
$nodeinfo['services'] = array(
- 'inbound' => array(),
+ 'inbound' => array(),
'outbound' => array(),
);
$nodeinfo['metadata'] = array(
- 'nodeName' => \get_bloginfo( 'name' ),
+ 'nodeName' => \get_bloginfo( 'name' ),
'nodeDescription' => \get_bloginfo( 'description' ),
- 'nodeIcon' => \get_site_icon_url(),
+ 'nodeIcon' => \get_site_icon_url(),
);
return new WP_REST_Response( $nodeinfo, 200 );
}
/**
- * Render NodeInfo file
+ * Render NodeInfo file.
*
- * @param WP_REST_Request $request
- *
- * @return WP_REST_Response
+ * @return WP_REST_Response The JSON profile of the NodeInfo.
*/
- public static function nodeinfo2( $request ) {
- /*
- * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ public static function nodeinfo2() {
+ /**
+ * Action triggered prior to the ActivityPub profile being created and sent to the client.
*/
\do_action( 'activitypub_rest_nodeinfo2_pre' );
$nodeinfo = array();
$nodeinfo['version'] = '2.0';
- $nodeinfo['server'] = array(
- 'baseUrl' => \home_url( '/' ),
- 'name' => \get_bloginfo( 'name' ),
+ $nodeinfo['server'] = array(
+ 'baseUrl' => \home_url( '/' ),
+ 'name' => \get_bloginfo( 'name' ),
'software' => 'wordpress',
- 'version' => get_masked_wp_version(),
+ 'version' => get_masked_wp_version(),
);
- $posts = \wp_count_posts();
+ $posts = \wp_count_posts();
$comments = \wp_count_comments();
$nodeinfo['usage'] = array(
- 'users' => array(
+ 'users' => array(
'total' => get_total_users(),
'activeMonth' => get_active_users( 1 ),
'activeHalfyear' => get_active_users( 6 ),
),
- 'localPosts' => (int) $posts->publish,
+ 'localPosts' => (int) $posts->publish,
'localComments' => (int) $comments->approved,
);
$nodeinfo['openRegistrations'] = false;
- $nodeinfo['protocols'] = array( 'activitypub' );
+ $nodeinfo['protocols'] = array( 'activitypub' );
$nodeinfo['services'] = array(
- 'inbound' => array(),
+ 'inbound' => array(),
'outbound' => array(),
);
@@ -163,21 +165,19 @@ class Nodeinfo {
}
/**
- * Render NodeInfo discovery file
- *
- * @param WP_REST_Request $request
+ * Render NodeInfo discovery file.
*
* @return WP_REST_Response
*/
- public static function discovery( $request ) {
- $discovery = array();
+ public static function discovery() {
+ $discovery = array();
$discovery['links'] = array(
array(
- 'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0',
+ 'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0',
'href' => get_rest_url_by_path( 'nodeinfo' ),
),
array(
- 'rel' => 'https://www.w3.org/ns/activitystreams#Application',
+ 'rel' => 'https://www.w3.org/ns/activitystreams#Application',
'href' => get_rest_url_by_path( 'application' ),
),
);
diff --git a/wp-content/plugins/activitypub/includes/rest/class-outbox.php b/wp-content/plugins/activitypub/includes/rest/class-outbox.php
index e0670301..461d4286 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-outbox.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-outbox.php
@@ -1,8 +1,13 @@
get_param( 'user_id' );
@@ -64,41 +69,43 @@ class Outbox {
$page = $request->get_param( 'page', 1 );
- /*
- * Action triggerd prior to the ActivityPub profile being created and sent to the client
+ /**
+ * Action triggered prior to the ActivityPub profile being created and sent to the client.
*/
\do_action( 'activitypub_rest_outbox_pre' );
$json = new stdClass();
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$json->{'@context'} = get_context();
- $json->id = get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) );
- $json->generator = 'http://wordpress.org/?v=' . get_masked_wp_version();
- $json->actor = $user->get_id();
- $json->type = 'OrderedCollectionPage';
- $json->partOf = get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) ); // phpcs:ignore
- $json->totalItems = 0; // phpcs:ignore
+ $json->id = get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) );
+ $json->generator = 'http://wordpress.org/?v=' . get_masked_wp_version();
+ $json->actor = $user->get_id();
+ $json->type = 'OrderedCollectionPage';
+ $json->partOf = get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) );
+ $json->totalItems = 0;
if ( $user_id > 0 ) {
- $count_posts = \count_user_posts( $user_id, $post_types, true );
- $json->totalItems = \intval( $count_posts ); // phpcs:ignore
+ $count_posts = \count_user_posts( $user_id, $post_types, true );
+ $json->totalItems = \intval( $count_posts );
} else {
foreach ( $post_types as $post_type ) {
- $count_posts = \wp_count_posts( $post_type );
- $json->totalItems += \intval( $count_posts->publish ); // phpcs:ignore
+ $count_posts = \wp_count_posts( $post_type );
+ $json->totalItems += \intval( $count_posts->publish );
}
}
- $json->first = \add_query_arg( 'page', 1, $json->partOf ); // phpcs:ignore
- $json->last = \add_query_arg( 'page', \ceil ( $json->totalItems / 10 ), $json->partOf ); // phpcs:ignore
+ $json->first = \add_query_arg( 'page', 1, $json->partOf );
+ $json->last = \add_query_arg( 'page', \ceil( $json->totalItems / 10 ), $json->partOf );
- if ( $page && ( ( \ceil ( $json->totalItems / 10 ) ) > $page ) ) { // phpcs:ignore
- $json->next = \add_query_arg( 'page', $page + 1, $json->partOf ); // phpcs:ignore
+ if ( $page && ( ( \ceil( $json->totalItems / 10 ) ) > $page ) ) {
+ $json->next = \add_query_arg( 'page', $page + 1, $json->partOf );
}
- if ( $page && ( $page > 1 ) ) { // phpcs:ignore
- $json->prev = \add_query_arg( 'page', $page - 1, $json->partOf ); // phpcs:ignore
+ if ( $page && ( $page > 1 ) ) {
+ $json->prev = \add_query_arg( 'page', $page - 1, $json->partOf );
}
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
if ( $page ) {
$posts = \get_posts(
@@ -121,15 +128,19 @@ class Outbox {
$activity = new Activity();
$activity->set_type( 'Create' );
$activity->set_object( $post );
- $json->orderedItems[] = $activity->to_array( false ); // phpcs:ignore
+ $json->orderedItems[] = $activity->to_array( false ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
}
}
- // filter output
+ /**
+ * Filter the ActivityPub outbox array.
+ *
+ * @param array $json The ActivityPub outbox array.
+ */
$json = \apply_filters( 'activitypub_rest_outbox_array', $json );
- /*
- * Action triggerd after the ActivityPub profile has been created and sent to the client
+ /**
+ * Action triggered after the ActivityPub profile has been created and sent to the client
*/
\do_action( 'activitypub_outbox_post' );
@@ -140,21 +151,21 @@ class Outbox {
}
/**
- * The supported parameters
+ * The supported parameters.
*
- * @return array list of parameters
+ * @return array List of parameters.
*/
public static function request_parameters() {
$params = array();
$params['page'] = array(
- 'type' => 'integer',
+ 'type' => 'integer',
'default' => 1,
);
$params['user_id'] = array(
'required' => true,
- 'type' => 'string',
+ 'type' => 'string',
);
return $params;
diff --git a/wp-content/plugins/activitypub/includes/rest/class-server.php b/wp-content/plugins/activitypub/includes/rest/class-server.php
index d03f6c17..aac34982 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-server.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-server.php
@@ -1,14 +1,19 @@
get_route();
- // check if it is an activitypub request and exclude webfinger and nodeinfo endpoints
+ // Check if it is an activitypub request and exclude webfinger and nodeinfo endpoints.
if (
! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE ) ||
\str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'webfinger' ) ||
@@ -90,13 +100,13 @@ class Server {
}
/**
- * Filter to defer signature verification
+ * Filter to defer signature verification.
*
* Skip signature verification for debugging purposes or to reduce load for
* certain Activity-Types, like "Delete".
*
- * @param bool $defer Whether to defer signature verification.
- * @param WP_REST_Request $request The request used to generate the response.
+ * @param bool $defer Whether to defer signature verification.
+ * @param \WP_REST_Request $request The request used to generate the response.
*
* @return bool Whether to defer signature verification.
*/
@@ -107,9 +117,9 @@ class Server {
}
if (
- // POST-Requests are always signed
+ // POST-Requests are always signed.
'GET' !== $request->get_method() ||
- // GET-Requests only require a signature in secure mode
+ // GET-Requests only require a signature in secure mode.
( 'GET' === $request->get_method() && ACTIVITYPUB_AUTHORIZED_FETCH )
) {
$verified_request = Signature::verify_http_signature( $request );
@@ -124,4 +134,51 @@ class Server {
return $response;
}
+
+ /**
+ * Callback function to validate incoming ActivityPub requests
+ *
+ * @param WP_REST_Response|\WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
+ * Usually a WP_REST_Response or WP_Error.
+ * @param array $handler Route handler used for the request.
+ * @param \WP_REST_Request $request Request used to generate the response.
+ *
+ * @return mixed|WP_Error The response, error, or modified response.
+ */
+ public static function validate_activitypub_requests( $response, $handler, $request ) {
+ if ( 'HEAD' === $request->get_method() ) {
+ return $response;
+ }
+
+ $route = $request->get_route();
+
+ if (
+ \is_wp_error( $response ) ||
+ ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE )
+ ) {
+ return $response;
+ }
+
+ $params = $request->get_json_params();
+
+ // Type is required for ActivityPub requests, so it fail later in the process.
+ if ( ! isset( $params['type'] ) ) {
+ return $response;
+ }
+
+ if (
+ ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS &&
+ in_array( $params['type'], array( 'Create', 'Like', 'Announce' ), true )
+ ) {
+ return new WP_Error(
+ 'activitypub_server_does_not_accept_incoming_interactions',
+ \__( 'This server does not accept incoming interactions.', 'activitypub' ),
+ // We have to use a 2XX status code here, because otherwise the response will be
+ // treated as an error and Mastodon might block this WordPress instance.
+ array( 'status' => 202 )
+ );
+ }
+
+ return $response;
+ }
}
diff --git a/wp-content/plugins/activitypub/includes/rest/class-webfinger.php b/wp-content/plugins/activitypub/includes/rest/class-webfinger.php
index 48224e18..868ed50d 100644
--- a/wp-content/plugins/activitypub/includes/rest/class-webfinger.php
+++ b/wp-content/plugins/activitypub/includes/rest/class-webfinger.php
@@ -1,12 +1,16 @@
'*',
- 'Content-Type' => 'application/jrd+json; charset=' . get_option( 'blog_charset' ),
+ 'Content-Type' => 'application/jrd+json; charset=' . get_option( 'blog_charset' ),
)
);
}
/**
- * The supported parameters
+ * The supported parameters.
*
* @return array list of parameters
*/
@@ -89,8 +89,8 @@ class Webfinger {
$params['resource'] = array(
'required' => true,
- 'type' => 'string',
- 'pattern' => '^(acct:)|^(https?://)(.+)$',
+ 'type' => 'string',
+ 'pattern' => '^(acct:)|^(https?://)(.+)$',
);
return $params;
@@ -99,47 +99,17 @@ class Webfinger {
/**
* Get the WebFinger profile.
*
- * @param string $resource the WebFinger resource.
+ * @param string $webfinger the WebFinger resource.
*
- * @return array the WebFinger profile.
+ * @return array|\WP_Error The WebFinger profile or WP_Error if not found.
*/
- public static function get_profile( $resource ) {
- $user = User_Collection::get_by_resource( $resource );
-
- if ( \is_wp_error( $user ) ) {
- return $user;
- }
-
- $aliases = array(
- $user->get_url(),
- $user->get_alternate_url(),
- );
-
- $aliases = array_unique( $aliases );
-
- $profile = array(
- 'subject' => sprintf( 'acct:%s', $user->get_webfinger() ),
- 'aliases' => array_values( array_unique( $aliases ) ),
- 'links' => array(
- array(
- 'rel' => 'self',
- 'type' => 'application/activity+json',
- 'href' => $user->get_url(),
- ),
- array(
- 'rel' => 'http://webfinger.net/rel/profile-page',
- 'type' => 'text/html',
- 'href' => $user->get_url(),
- ),
- ),
- );
-
- if ( 'Person' !== $user->get_type() ) {
- $profile['links'][0]['properties'] = array(
- 'https://www.w3.org/ns/activitystreams#type' => $user->get_type(),
- );
- }
-
- return $profile;
+ public static function get_profile( $webfinger ) {
+ /**
+ * Filter the WebFinger data.
+ *
+ * @param array $data The WebFinger data.
+ * @param string $webfinger The WebFinger resource.
+ */
+ return apply_filters( 'webfinger_data', array(), $webfinger );
}
}
diff --git a/wp-content/plugins/activitypub/includes/table/class-followers.php b/wp-content/plugins/activitypub/includes/table/class-followers.php
index df9747bd..991f97c1 100644
--- a/wp-content/plugins/activitypub/includes/table/class-followers.php
+++ b/wp-content/plugins/activitypub/includes/table/class-followers.php
@@ -1,4 +1,10 @@
id === 'settings_page_activitypub' ) {
$this->user_id = Users::BLOG_USER_ID;
@@ -30,6 +47,11 @@ class Followers extends WP_List_Table {
);
}
+ /**
+ * Get columns.
+ *
+ * @return array
+ */
public function get_columns() {
return array(
'cb' => '',
@@ -42,16 +64,22 @@ class Followers extends WP_List_Table {
);
}
+ /**
+ * Returns sortable columns.
+ *
+ * @return array
+ */
public function get_sortable_columns() {
- $sortable_columns = array(
+ return array(
'post_title' => array( 'post_title', true ),
'modified' => array( 'modified', false ),
'published' => array( 'published', false ),
);
-
- return $sortable_columns;
}
+ /**
+ * Prepare items.
+ */
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array();
@@ -64,26 +92,22 @@ class Followers extends WP_List_Table {
$args = array();
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['orderby'] ) ) {
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$args['orderby'] = sanitize_text_field( wp_unslash( $_GET['orderby'] ) );
}
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['order'] ) ) {
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$args['order'] = sanitize_text_field( wp_unslash( $_GET['order'] ) );
}
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['s'] ) && isset( $_REQUEST['_wpnonce'] ) ) {
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
if ( wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$args['s'] = sanitize_text_field( wp_unslash( $_GET['s'] ) );
}
}
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended
$followers_with_count = FollowerCollection::get_followers_with_count( $this->user_id, $per_page, $page_num, $args );
$followers = $followers_with_count['followers'];
@@ -113,12 +137,24 @@ class Followers extends WP_List_Table {
}
}
+ /**
+ * Returns bulk actions.
+ *
+ * @return array
+ */
public function get_bulk_actions() {
return array(
'delete' => __( 'Delete', 'activitypub' ),
);
}
+ /**
+ * Column default.
+ *
+ * @param array $item Item.
+ * @param string $column_name Column name.
+ * @return string
+ */
public function column_default( $item, $column_name ) {
if ( ! array_key_exists( $column_name, $item ) ) {
return __( 'None', 'activitypub' );
@@ -126,6 +162,12 @@ class Followers extends WP_List_Table {
return $item[ $column_name ];
}
+ /**
+ * Column avatar.
+ *
+ * @param array $item Item.
+ * @return string
+ */
public function column_avatar( $item ) {
return sprintf(
'',
@@ -133,45 +175,63 @@ class Followers extends WP_List_Table {
);
}
+ /**
+ * Column url.
+ *
+ * @param array $item Item.
+ * @return string
+ */
public function column_url( $item ) {
return sprintf(
'%s',
- $item['url'],
+ esc_url( $item['url'] ),
$item['url']
);
}
+ /**
+ * Column cb.
+ *
+ * @param array $item Item.
+ * @return string
+ */
public function column_cb( $item ) {
return sprintf( '', esc_attr( $item['identifier'] ) );
}
+ /**
+ * Process action.
+ */
public function process_action() {
if ( ! isset( $_REQUEST['followers'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
- return false;
+ return;
}
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
if ( ! wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
- return false;
+ return;
}
if ( ! current_user_can( 'edit_user', $this->user_id ) ) {
- return false;
+ return;
}
- $followers = $_REQUEST['followers']; // phpcs:ignore
+ $followers = $_REQUEST['followers']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
- switch ( $this->current_action() ) {
- case 'delete':
- if ( ! is_array( $followers ) ) {
- $followers = array( $followers );
- }
- foreach ( $followers as $follower ) {
- FollowerCollection::remove_follower( $this->user_id, $follower );
- }
- break;
+ if ( $this->current_action() === 'delete' ) {
+ if ( ! is_array( $followers ) ) {
+ $followers = array( $followers );
+ }
+ foreach ( $followers as $follower ) {
+ FollowerCollection::remove_follower( $this->user_id, $follower );
+ }
}
}
+ /**
+ * Returns user count.
+ *
+ * @return int
+ */
public function get_user_count() {
return FollowerCollection::count_followers( $this->user_id );
}
diff --git a/wp-content/plugins/activitypub/includes/transformer/class-attachment.php b/wp-content/plugins/activitypub/includes/transformer/class-attachment.php
index 2fc46350..98aaf8bf 100644
--- a/wp-content/plugins/activitypub/includes/transformer/class-attachment.php
+++ b/wp-content/plugins/activitypub/includes/transformer/class-attachment.php
@@ -1,10 +1,14 @@
wp_object->ID );
$media_type = preg_replace( '/(\/[a-zA-Z]+)/i', '', $mime_type );
+ $type = '';
switch ( $media_type ) {
case 'audio':
diff --git a/wp-content/plugins/activitypub/includes/transformer/class-base.php b/wp-content/plugins/activitypub/includes/transformer/class-base.php
index 5041fa96..6d4c202a 100644
--- a/wp-content/plugins/activitypub/includes/transformer/class-base.php
+++ b/wp-content/plugins/activitypub/includes/transformer/class-base.php
@@ -1,15 +1,21 @@
wp_object = $wp_object;
@@ -49,9 +55,9 @@ abstract class Base {
/**
* Transform all properties with available get(ter) functions.
*
- * @param Base_Object|object $object
+ * @param Base_Object|object $activitypub_object The ActivityPub Object.
*
- * @return Base_Object|object $object
+ * @return Base_Object|object
*/
protected function transform_object_properties( $activitypub_object ) {
$vars = $activitypub_object->get_object_var_keys();
@@ -75,13 +81,12 @@ abstract class Base {
/**
* Transform the WordPress Object into an ActivityPub Object.
*
- * @return Activitypub\Activity\Base_Object
+ * @return Base_Object|object The ActivityPub Object.
*/
public function to_object() {
$activitypub_object = new Base_Object();
- $activitypub_object = $this->transform_object_properties( $activitypub_object );
- return $activitypub_object;
+ return $this->transform_object_properties( $activitypub_object );
}
/**
@@ -89,7 +94,7 @@ abstract class Base {
*
* @param string $type The Activity-Type.
*
- * @return \Activitypub\Activity\Activity The Activity.
+ * @return Activity The Activity.
*/
public function to_activity( $type ) {
$object = $this->to_object();
@@ -100,7 +105,7 @@ abstract class Base {
// Pre-fill the Activity with data (for example cc and to).
$activity->set_object( $object );
- // Use simple Object (only ID-URI) for Like and Announce
+ // Use simple Object (only ID-URI) for Like and Announce.
if ( in_array( $type, array( 'Like', 'Announce' ), true ) ) {
$activity->set_object( $object->get_id() );
}
@@ -108,17 +113,27 @@ abstract class Base {
return $activity;
}
+ /**
+ * Get the ID of the WordPress Object.
+ */
+ abstract protected function get_id();
+
+ /**
+ * Get the replies Collection.
+ */
+ public function get_replies() {
+ return Replies::get_collection( $this->wp_object );
+ }
+
/**
* Returns the ID of the WordPress Object.
- *
- * @return int The ID of the WordPress Object
*/
abstract public function get_wp_user_id();
/**
* Change the User-ID of the WordPress Post.
*
- * @return int The User-ID of the WordPress Post
+ * @param int $user_id The new user ID.
*/
abstract public function change_wp_user_id( $user_id );
}
diff --git a/wp-content/plugins/activitypub/includes/transformer/class-comment.php b/wp-content/plugins/activitypub/includes/transformer/class-comment.php
index 72cf11f6..7f0d3f68 100644
--- a/wp-content/plugins/activitypub/includes/transformer/class-comment.php
+++ b/wp-content/plugins/activitypub/includes/transformer/class-comment.php
@@ -1,21 +1,23 @@
wp_object->user_id = $user_id;
}
/**
- * Transforms the WP_Comment object to an ActivityPub Object
+ * Transforms the WP_Comment object to an ActivityPub Object.
*
* @see \Activitypub\Activity\Base_Object
*
- * @return \Activitypub\Activity\Base_Object The ActivityPub Object
+ * @return \Activitypub\Activity\Base_Object The ActivityPub Object.
*/
public function to_object() {
$comment = $this->wp_object;
@@ -106,41 +108,49 @@ class Comment extends Base {
* @return string The content.
*/
protected function get_content() {
- // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$comment = $this->wp_object;
$content = $comment->comment_content;
+ /**
+ * Filter the content of the comment.
+ *
+ * @param string $content The content of the comment.
+ * @param \WP_Comment $comment The comment object.
+ * @param array $args The arguments.
+ *
+ * @return string The filtered content of the comment.
+ */
$content = \apply_filters( 'comment_text', $content, $comment, array() );
$content = \preg_replace( '/[\n\r\t]/', '', $content );
$content = \trim( $content );
- $content = \apply_filters( 'activitypub_the_content', $content, $comment );
- return $content;
+ /**
+ * Filter the content of the comment.
+ *
+ * @param string $content The content of the comment.
+ * @param \WP_Comment $comment The comment object.
+ *
+ * @return string The filtered content of the comment.
+ */
+ return \apply_filters( 'activitypub_the_content', $content, $comment );
}
/**
* Returns the in-reply-to for the ActivityPub Item.
*
- * @return int The URL of the in-reply-to.
+ * @return false|string|null The URL of the in-reply-to.
*/
protected function get_in_reply_to() {
- $comment = $this->wp_object;
-
+ $comment = $this->wp_object;
$parent_comment = null;
- $in_reply_to = null;
if ( $comment->comment_parent ) {
$parent_comment = \get_comment( $comment->comment_parent );
}
if ( $parent_comment ) {
- $comment_meta = \get_comment_meta( $parent_comment->comment_ID );
-
- if ( ! empty( $comment_meta['source_id'][0] ) ) {
- $in_reply_to = $comment_meta['source_id'][0];
- } elseif ( ! empty( $comment_meta['source_url'][0] ) ) {
- $in_reply_to = $comment_meta['source_url'][0];
- } elseif ( ! empty( $parent_comment->user_id ) ) {
+ $in_reply_to = Comment_Utils::get_source_id( $parent_comment->comment_ID );
+ if ( ! $in_reply_to && ! empty( $parent_comment->user_id ) ) {
$in_reply_to = Comment_Utils::generate_id( $parent_comment );
}
} else {
@@ -196,7 +206,7 @@ class Comment extends Base {
$mentions = $this->get_mentions();
if ( $mentions ) {
foreach ( $mentions as $mention => $url ) {
- $tag = array(
+ $tag = array(
'type' => 'Mention',
'href' => \esc_url( $url ),
'name' => \esc_html( $mention ),
@@ -216,6 +226,15 @@ class Comment extends Base {
protected function get_mentions() {
\add_filter( 'activitypub_extract_mentions', array( $this, 'extract_reply_context' ) );
+ /**
+ * Filter the mentions in the comment.
+ *
+ * @param array $mentions The list of mentions.
+ * @param string $content The content of the comment.
+ * @param \WP_Comment $comment The comment object.
+ *
+ * @return array The filtered list of mentions.
+ */
return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_object->comment_content, $this->wp_object );
}
@@ -227,7 +246,7 @@ class Comment extends Base {
protected function get_comment_ancestors() {
$ancestors = get_comment_ancestors( $this->wp_object );
- // Now that we have the full tree of ancestors, only return the ones received from the fediverse
+ // Now that we have the full tree of ancestors, only return the ones received from the fediverse.
return array_filter(
$ancestors,
function ( $comment_id ) {
@@ -240,12 +259,12 @@ class Comment extends Base {
* Collect all other Users that participated in this comment-thread
* to send them a notification about the new reply.
*
- * @param array $mentions The already mentioned ActivityPub users
+ * @param array $mentions The already mentioned ActivityPub users.
*
* @return array The list of all Repliers.
*/
public function extract_reply_context( $mentions ) {
- // Check if `$this->wp_object` is a WP_Comment
+ // Check if `$this->wp_object` is a WP_Comment.
if ( 'WP_Comment' !== get_class( $this->wp_object ) ) {
return $mentions;
}
@@ -260,7 +279,7 @@ class Comment extends Base {
if ( $comment && ! empty( $comment->comment_author_url ) ) {
$acct = Webfinger::uri_to_acct( $comment->comment_author_url );
if ( $acct && ! is_wp_error( $acct ) ) {
- $acct = str_replace( 'acct:', '@', $acct );
+ $acct = str_replace( 'acct:', '@', $acct );
$mentions[ $acct ] = $comment->comment_author_url;
}
}
@@ -281,9 +300,9 @@ class Comment extends Base {
/**
* Filter the locale of the comment.
*
- * @param string $lang The locale of the comment.
- * @param int $comment_id The comment ID.
- * @param WP_Post $post The comment object.
+ * @param string $lang The locale of the comment.
+ * @param int $comment_id The comment ID.
+ * @param \WP_Post $post The comment object.
*
* @return string The filtered locale of the comment.
*/
diff --git a/wp-content/plugins/activitypub/includes/transformer/class-factory.php b/wp-content/plugins/activitypub/includes/transformer/class-factory.php
index b21de7f9..a619423a 100644
--- a/wp-content/plugins/activitypub/includes/transformer/class-factory.php
+++ b/wp-content/plugins/activitypub/includes/transformer/class-factory.php
@@ -1,26 +1,31 @@
post_type ) {
- return new Attachment( $object );
+ if ( 'attachment' === $data->post_type ) {
+ return new Attachment( $data );
}
- return new Post( $object );
+ return new Post( $data );
case 'WP_Comment':
- return new Comment( $object );
+ return new Comment( $data );
default:
return null;
}
diff --git a/wp-content/plugins/activitypub/includes/transformer/class-post.php b/wp-content/plugins/activitypub/includes/transformer/class-post.php
index 82f69dcc..f18693fb 100644
--- a/wp-content/plugins/activitypub/includes/transformer/class-post.php
+++ b/wp-content/plugins/activitypub/includes/transformer/class-post.php
@@ -1,20 +1,26 @@
wp_object->post_author = $user_id;
@@ -52,42 +67,54 @@ class Post extends Base {
* @return \Activitypub\Activity\Base_Object The ActivityPub Object
*/
public function to_object() {
- $post = $this->wp_object;
+ $post = $this->wp_object;
$object = parent::to_object();
- $published = \strtotime( $post->post_date_gmt );
-
- $object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
-
- $updated = \strtotime( $post->post_modified_gmt );
-
- if ( $updated > $published ) {
- $object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
+ $content_warning = get_content_warning( $post );
+ if ( ! empty( $content_warning ) ) {
+ $object->set_sensitive( true );
+ $object->set_summary( $content_warning );
+ $object->set_summary_map( null );
}
- $object->set_content_map(
- array(
- $this->get_locale() => $this->get_content(),
- )
- );
- $path = sprintf( 'actors/%d/followers', intval( $post->post_author ) );
-
- $object->set_to(
- array(
- 'https://www.w3.org/ns/activitystreams#Public',
- get_rest_url_by_path( $path ),
- )
- );
-
return $object;
}
+ /**
+ * Returns the User-Object of the Author of the Post.
+ *
+ * If `single_user` mode is enabled, the Blog-User is returned.
+ *
+ * @return \Activitypub\Activity\Actor The User-Object.
+ */
+ protected function get_actor_object() {
+ if ( $this->actor_object ) {
+ return $this->actor_object;
+ }
+
+ $blog_user = new Blog();
+ $this->actor_object = $blog_user;
+
+ if ( is_single_user() ) {
+ return $blog_user;
+ }
+
+ $user = Users::get_by_id( $this->wp_object->post_author );
+
+ if ( $user && ! is_wp_error( $user ) ) {
+ $this->actor_object = $user;
+ return $user;
+ }
+
+ return $blog_user;
+ }
+
/**
* Returns the ID of the Post.
*
* @return string The Posts ID.
*/
- public function get_id() {
+ protected function get_id() {
return $this->get_url();
}
@@ -99,13 +126,21 @@ class Post extends Base {
public function get_url() {
$post = $this->wp_object;
- if ( 'trash' === get_post_status( $post ) ) {
- $permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
- } elseif ( 'draft' === get_post_status( $post ) && get_sample_permalink( $post->ID ) ) {
- $sample = get_sample_permalink( $post->ID );
- $permalink = str_replace( array( '%pagename%', '%postname%' ), $sample[1], $sample[0] );
- } else {
- $permalink = \get_permalink( $post );
+ switch ( \get_post_status( $post ) ) {
+ case 'trash':
+ $permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
+ break;
+ case 'draft':
+ // Get_sample_permalink is in wp-admin, not always loaded.
+ if ( ! \function_exists( '\get_sample_permalink' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/post.php';
+ }
+ $sample = \get_sample_permalink( $post->ID );
+ $permalink = \str_replace( array( '%pagename%', '%postname%' ), $sample[1], $sample[0] );
+ break;
+ default:
+ $permalink = \get_permalink( $post );
+ break;
}
return \esc_url( $permalink );
@@ -119,19 +154,7 @@ class Post extends Base {
* @return string The User-URL.
*/
protected function get_attributed_to() {
- $blog_user = new Blog();
-
- if ( is_single_user() ) {
- return $blog_user->get_url();
- }
-
- $user = Users::get_by_id( $this->wp_object->post_author );
-
- if ( $user && ! is_wp_error( $user ) ) {
- return $user->get_url();
- }
-
- return $blog_user->get_url();
+ return $this->get_actor_object()->get_url();
}
/**
@@ -161,7 +184,7 @@ class Post extends Base {
);
$id = $this->wp_object->ID;
- // list post thumbnail first if this post has one
+ // List post thumbnail first if this post has one.
if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
$media['image'][] = array( 'id' => \get_post_thumbnail_id( $id ) );
}
@@ -179,155 +202,27 @@ class Post extends Base {
$media = \array_intersect_key( $media, $unique_ids );
$media = \array_slice( $media, 0, $max_media );
- return \array_filter( \array_map( array( self::class, 'wp_attachment_to_activity_attachment' ), $media ) );
- }
+ /**
+ * Filter the attachment IDs for a post.
+ *
+ * @param array $media The media array grouped by type.
+ * @param WP_Post $this->wp_object The post object.
+ *
+ * @return array The filtered attachment IDs.
+ */
+ $media = \apply_filters( 'activitypub_attachment_ids', $media, $this->wp_object );
- /**
- * Get media attachments from blocks. They will be formatted as ActivityPub attachments, not as WP attachments.
- *
- * @param array $media The media array grouped by type.
- * @param int $max_media The maximum number of attachments to return.
- *
- * @return array The attachments.
- */
- protected function get_block_attachments( $media, $max_media ) {
- // max media can't be negative or zero
- if ( $max_media <= 0 ) {
- return array();
- }
+ $attachments = \array_filter( \array_map( array( self::class, 'wp_attachment_to_activity_attachment' ), $media ) );
- $blocks = \parse_blocks( $this->wp_object->post_content );
- $media = self::get_media_from_blocks( $blocks, $media );
-
- return $media;
- }
-
- /**
- * Get image attachments from the classic editor.
- * This is imperfect as the contained images aren't necessarily the
- * same as the attachments.
- *
- * @param int $max_images The maximum number of images to return.
- *
- * @return array The attachment IDs.
- */
- protected function get_classic_editor_image_attachments( $max_images ) {
- // max images can't be negative or zero
- if ( $max_images <= 0 ) {
- return array();
- }
-
- $images = array();
- $query = new \WP_Query(
- array(
- 'post_parent' => $this->wp_object->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, $images, true ) ) {
- $images[] = array( 'id' => $attachment->ID );
- }
- }
-
- return $images;
- }
-
- /**
- * Get image embeds from the classic editor by parsing HTML.
- *
- * @param int $max_images The maximum number of images to return.
- *
- * @return array The attachments.
- */
- protected function get_classic_editor_image_embeds( $max_images ) {
- // if someone calls that function directly, bail
- if ( ! \class_exists( '\WP_HTML_Tag_Processor' ) ) {
- return array();
- }
-
- // max images can't be negative or zero
- if ( $max_images <= 0 ) {
- return array();
- }
-
- $images = array();
- $base = \wp_get_upload_dir()['baseurl'];
- $content = \get_post_field( 'post_content', $this->wp_object );
- $tags = new \WP_HTML_Tag_Processor( $content );
-
- // This linter warning is a false positive - we have to
- // re-count each time here as we modify $images.
- // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
- while ( $tags->next_tag( 'img' ) && ( \count( $images ) <= $max_images ) ) {
- $src = $tags->get_attribute( 'src' );
-
- // If the img source is in our uploads dir, get the
- // associated ID. Note: if there's a -500x500
- // type suffix, we remove it, but we try the original
- // first in case the original image is actually called
- // that. Likewise, we try adding the -scaled suffix for
- // the case that this is a small version of an image
- // that was big enough to get scaled down on upload:
- // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
- if ( null !== $src && \str_starts_with( $src, $base ) ) {
- $img_id = \attachment_url_to_postid( $src );
-
- if ( 0 === $img_id ) {
- $count = 0;
- $src = preg_replace( '/-(?:\d+x\d+)(\.[a-zA-Z]+)$/', '$1', $src, 1, $count );
- if ( $count > 0 ) {
- $img_id = \attachment_url_to_postid( $src );
- }
- }
-
- if ( 0 === $img_id ) {
- $src = preg_replace( '/(\.[a-zA-Z]+)$/', '-scaled$1', $src );
- $img_id = \attachment_url_to_postid( $src );
- }
-
- if ( 0 !== $img_id ) {
- $images[] = array(
- 'id' => $img_id,
- 'alt' => $tags->get_attribute( 'alt' ),
- );
- }
- }
- }
-
- return $images;
- }
-
- /**
- * Get post images from the classic editor.
- * Note that audio/video attachments are only supported in the block editor.
- *
- * @param array $media The media array grouped by type.
- * @param int $max_images The maximum number of images to return.
- *
- * @return array The attachments.
- */
- protected function get_classic_editor_images( $media, $max_images ) {
- // max images can't be negative or zero
- if ( $max_images <= 0 ) {
- return array();
- }
-
- if ( \count( $media['image'] ) <= $max_images ) {
- if ( \class_exists( '\WP_HTML_Tag_Processor' ) ) {
- $media['image'] = \array_merge( $media['image'], $this->get_classic_editor_image_embeds( $max_images ) );
- } else {
- $media['image'] = \array_merge( $media['image'], $this->get_classic_editor_image_attachments( $max_images ) );
- }
- }
-
- return $media;
+ /**
+ * Filter the attachments for a post.
+ *
+ * @param array $attachments The attachments.
+ * @param WP_Post $this->wp_object The post object.
+ *
+ * @return array The filtered attachments.
+ */
+ return \apply_filters( 'activitypub_attachments', $attachments, $this->wp_object );
}
/**
@@ -345,11 +240,11 @@ class Post extends Base {
}
foreach ( $enclosures as $enclosure ) {
- // check if URL is an attachment
+ // Check if URL is an attachment.
$attachment_id = \attachment_url_to_postid( $enclosure['url'] );
if ( $attachment_id ) {
- $enclosure['id'] = $attachment_id;
- $enclosure['url'] = \wp_get_attachment_url( $attachment_id );
+ $enclosure['id'] = $attachment_id;
+ $enclosure['url'] = \wp_get_attachment_url( $attachment_id );
$enclosure['mediaType'] = \get_post_mime_type( $attachment_id );
}
@@ -372,17 +267,36 @@ class Post extends Base {
return $media;
}
+ /**
+ * Get media attachments from blocks. They will be formatted as ActivityPub attachments, not as WP attachments.
+ *
+ * @param array $media The media array grouped by type.
+ * @param int $max_media The maximum number of attachments to return.
+ *
+ * @return array The attachments.
+ */
+ protected function get_block_attachments( $media, $max_media ) {
+ // Max media can't be negative or zero.
+ if ( $max_media <= 0 ) {
+ return array();
+ }
+
+ $blocks = \parse_blocks( $this->wp_object->post_content );
+
+ return self::get_media_from_blocks( $blocks, $media );
+ }
+
/**
* Recursively get media IDs from blocks.
- * @param array $blocks The blocks to search for media IDs
- * @param array $media The media IDs to append new IDs to
- * @param int $max_media The maximum number of media to return.
+ *
+ * @param array $blocks The blocks to search for media IDs.
+ * @param array $media The media IDs to append new IDs to.
*
* @return array The image IDs.
*/
protected static function get_media_from_blocks( $blocks, $media ) {
foreach ( $blocks as $block ) {
- // recurse into inner blocks
+ // Recurse into inner blocks.
if ( ! empty( $block['innerBlocks'] ) ) {
$media = self::get_media_from_blocks( $block['innerBlocks'], $media );
}
@@ -443,22 +357,160 @@ class Post extends Base {
return $media;
}
+ /**
+ * Get post images from the classic editor.
+ * Note that audio/video attachments are only supported in the block editor.
+ *
+ * @param array $media The media array grouped by type.
+ * @param int $max_images The maximum number of images to return.
+ *
+ * @return array The attachments.
+ */
+ protected function get_classic_editor_images( $media, $max_images ) {
+ // Max images can't be negative or zero.
+ if ( $max_images <= 0 ) {
+ return array();
+ }
+
+ if ( \count( $media['image'] ) <= $max_images ) {
+ if ( \class_exists( '\WP_HTML_Tag_Processor' ) ) {
+ $media['image'] = \array_merge( $media['image'], $this->get_classic_editor_image_embeds( $max_images ) );
+ } else {
+ $media['image'] = \array_merge( $media['image'], $this->get_classic_editor_image_attachments( $max_images ) );
+ }
+ }
+
+ return $media;
+ }
+
+ /**
+ * Get image embeds from the classic editor by parsing HTML.
+ *
+ * @param int $max_images The maximum number of images to return.
+ *
+ * @return array The attachments.
+ */
+ protected function get_classic_editor_image_embeds( $max_images ) {
+ // If someone calls that function directly, bail.
+ if ( ! \class_exists( '\WP_HTML_Tag_Processor' ) ) {
+ return array();
+ }
+
+ // Max images can't be negative or zero.
+ if ( $max_images <= 0 ) {
+ return array();
+ }
+
+ $images = array();
+ $base = \wp_get_upload_dir()['baseurl'];
+ $content = \get_post_field( 'post_content', $this->wp_object );
+ $tags = new \WP_HTML_Tag_Processor( $content );
+
+ // This linter warning is a false positive - we have to re-count each time here as we modify $images.
+ // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
+ while ( $tags->next_tag( 'img' ) && ( \count( $images ) <= $max_images ) ) {
+ $src = $tags->get_attribute( 'src' );
+
+ /*
+ * If the img source is in our uploads dir, get the
+ * associated ID. Note: if there's a -500x500
+ * type suffix, we remove it, but we try the original
+ * first in case the original image is actually called
+ * that. Likewise, we try adding the -scaled suffix for
+ * the case that this is a small version of an image
+ * that was big enough to get scaled down on upload:
+ * https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
+ */
+ if ( null !== $src && \str_starts_with( $src, $base ) ) {
+ $img_id = \attachment_url_to_postid( $src );
+
+ if ( 0 === $img_id ) {
+ $count = 0;
+ $src = preg_replace( '/-(?:\d+x\d+)(\.[a-zA-Z]+)$/', '$1', $src, 1, $count );
+ if ( $count > 0 ) {
+ $img_id = \attachment_url_to_postid( $src );
+ }
+ }
+
+ if ( 0 === $img_id ) {
+ $src = preg_replace( '/(\.[a-zA-Z]+)$/', '-scaled$1', $src );
+ $img_id = \attachment_url_to_postid( $src );
+ }
+
+ if ( 0 !== $img_id ) {
+ $images[] = array(
+ 'id' => $img_id,
+ 'alt' => $tags->get_attribute( 'alt' ),
+ );
+ }
+ }
+ }
+
+ return $images;
+ }
+
+ /**
+ * Get image attachments from the classic editor.
+ * This is imperfect as the contained images aren't necessarily the
+ * same as the attachments.
+ *
+ * @param int $max_images The maximum number of images to return.
+ *
+ * @return array The attachment IDs.
+ */
+ protected function get_classic_editor_image_attachments( $max_images ) {
+ // Max images can't be negative or zero.
+ if ( $max_images <= 0 ) {
+ return array();
+ }
+
+ $images = array();
+ $query = new \WP_Query(
+ array(
+ 'post_parent' => $this->wp_object->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, $images, true ) ) {
+ $images[] = array( 'id' => $attachment->ID );
+ }
+ }
+
+ return $images;
+ }
+
/**
* Filter media IDs by object type.
*
- * @param array $media The media array grouped by type.
- * @param string $type The object type.
+ * @param array $media The media array grouped by type.
+ * @param string $type The object type.
+ * @param WP_Post $wp_object The post object.
*
* @return array The filtered media IDs.
*/
protected static function filter_media_by_object_type( $media, $type, $wp_object ) {
+ /**
+ * Filter the object type for media attachments.
+ *
+ * @param string $type The object type.
+ * @param WP_Post $wp_object The post object.
+ *
+ * @return string The filtered object type.
+ */
$type = \apply_filters( 'filter_media_by_object_type', \strtolower( $type ), $wp_object );
if ( ! empty( $media[ $type ] ) ) {
return $media[ $type ];
}
- return array_filter( array_merge( array(), ...array_values( $media ) ) );
+ return array_filter( array_merge( ...array_values( $media ) ) );
}
/**
@@ -477,7 +529,7 @@ class Post extends Base {
$attachment = array();
$mime_type = \get_post_mime_type( $id );
$mime_type_parts = \explode( '/', $mime_type );
- // switching on image/audio/video
+ // Switching on image/audio/video.
switch ( $mime_type_parts[0] ) {
case 'image':
$image_size = 'large';
@@ -524,16 +576,24 @@ class Post extends Base {
'url' => \esc_url( \wp_get_attachment_url( $id ) ),
'name' => \esc_attr( \get_the_title( $id ) ),
);
- $meta = wp_get_attachment_metadata( $id );
- // height and width for videos
+ $meta = wp_get_attachment_metadata( $id );
+ // Height and width for videos.
if ( isset( $meta['width'] ) && isset( $meta['height'] ) ) {
- $attachment['width'] = \esc_attr( $meta['width'] );
+ $attachment['width'] = \esc_attr( $meta['width'] );
$attachment['height'] = \esc_attr( $meta['height'] );
}
// @todo: add `icon` support for audio/video attachments. Maybe use post thumbnail?
break;
}
+ /**
+ * Filter the attachment for a post.
+ *
+ * @param array $attachment The attachment.
+ * @param int $id The attachment ID.
+ *
+ * @return array The filtered attachment.
+ */
return \apply_filters( 'activitypub_attachment', $attachment, $id );
}
@@ -589,7 +649,7 @@ class Post extends Base {
}
// Default to Article.
- $object_type = 'Note';
+ $object_type = 'Article';
$post_format = 'standard';
if ( \get_theme_support( 'post-formats' ) ) {
@@ -613,7 +673,7 @@ class Post extends Base {
$object_type = 'Page';
break;
default:
- $object_type = 'Note';
+ $object_type = 'Article';
break;
}
@@ -640,7 +700,11 @@ class Post extends Base {
return $cc;
}
-
+ /**
+ * Returns the Audience for the Post.
+ *
+ * @return string|null The audience.
+ */
public function get_audience() {
if ( is_single_user() ) {
return null;
@@ -663,7 +727,7 @@ class Post extends Base {
$post_tags = \get_the_tags( $this->wp_object->ID );
if ( $post_tags ) {
foreach ( $post_tags as $post_tag ) {
- $tag = array(
+ $tag = array(
'type' => 'Hashtag',
'href' => \esc_url( \get_tag_link( $post_tag->term_id ) ),
'name' => esc_hashtag( $post_tag->name ),
@@ -675,7 +739,7 @@ class Post extends Base {
$mentions = $this->get_mentions();
if ( $mentions ) {
foreach ( $mentions as $mention => $url ) {
- $tag = array(
+ $tag = array(
'type' => 'Mention',
'href' => \esc_url( $url ),
'name' => \esc_html( $mention ),
@@ -705,24 +769,7 @@ class Post extends Base {
return \__( '(This post is being modified)', 'activitypub' );
}
- $content = \get_post_field( 'post_content', $this->wp_object->ID );
- $content = \html_entity_decode( $content );
- $content = \wp_strip_all_tags( $content );
- $content = \trim( $content );
- $content = \preg_replace( '/\R+/m', "\n\n", $content );
- $content = \preg_replace( '/[\r\t]/', '', $content );
-
- $excerpt_more = \apply_filters( 'activitypub_excerpt_more', '[...]' );
- $length = 500;
- $length = $length - strlen( $excerpt_more );
-
- if ( \strlen( $content ) > $length ) {
- $content = \wordwrap( $content, $length, '' );
- $content = \explode( '', $content, 2 );
- $content = $content[0];
- }
-
- return $content . ' ' . $excerpt_more;
+ return generate_post_summary( $this->wp_object );
}
/**
@@ -759,6 +806,8 @@ class Post extends Base {
* @return string The content.
*/
protected function get_content() {
+ add_filter( 'activitypub_reply_block', '__return_empty_string' );
+
// Remove Content from drafts.
if ( 'draft' === \get_post_status( $this->wp_object ) ) {
return \__( '(This post is being modified)', 'activitypub' );
@@ -794,7 +843,7 @@ class Post extends Base {
$content = \apply_filters( 'activitypub_the_content', $content, $post );
- // Don't need these any more, should never appear in a post.
+ // Don't need these anymore, should never appear in a post.
Shortcodes::unregister();
return $content;
@@ -819,7 +868,8 @@ class Post extends Base {
$template = "[ap_content]\n\n[ap_permalink type=\"html\"]\n\n[ap_hashtags]";
break;
default:
- $template = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
+ $content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
+ $template = empty( $content ) ? ACTIVITYPUB_CUSTOM_POST_CONTENT : $content;
break;
}
@@ -838,7 +888,21 @@ class Post extends Base {
* @return array The list of @-Mentions.
*/
protected function get_mentions() {
- return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_object->post_content, $this->wp_object );
+ /**
+ * Filter the mentions in the post content.
+ *
+ * @param array $mentions The mentions.
+ * @param string $content The post content.
+ * @param WP_Post $post The post object.
+ *
+ * @return array The filtered mentions.
+ */
+ return apply_filters(
+ 'activitypub_extract_mentions',
+ array(),
+ $this->wp_object->post_content . ' ' . $this->wp_object->post_excerpt,
+ $this->wp_object
+ );
}
/**
@@ -862,6 +926,108 @@ class Post extends Base {
return apply_filters( 'activitypub_post_locale', $lang, $post_id, $this->wp_object );
}
+ /**
+ * Returns the in-reply-to URL of the post.
+ *
+ * @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-inreplyto
+ *
+ * @return string|null The in-reply-to URL of the post.
+ */
+ public function get_in_reply_to() {
+ $blocks = \parse_blocks( $this->wp_object->post_content );
+
+ foreach ( $blocks as $block ) {
+ if ( 'activitypub/reply' === $block['blockName'] ) {
+ // We only support one reply block per post for now.
+ return $block['attrs']['url'];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the recipient of the post.
+ *
+ * @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-to
+ *
+ * @return array The recipient URLs of the post.
+ */
+ public function get_to() {
+ return array(
+ 'https://www.w3.org/ns/activitystreams#Public',
+ $this->get_actor_object()->get_followers(),
+ );
+ }
+
+ /**
+ * Returns the published date of the post.
+ *
+ * @return string The published date of the post.
+ */
+ public function get_published() {
+ $published = \strtotime( $this->wp_object->post_date_gmt );
+
+ return \gmdate( 'Y-m-d\TH:i:s\Z', $published );
+ }
+
+ /**
+ * Returns the updated date of the post.
+ *
+ * @return string|null The updated date of the post.
+ */
+ public function get_updated() {
+ $published = \strtotime( $this->wp_object->post_date_gmt );
+ $updated = \strtotime( $this->wp_object->post_modified_gmt );
+
+ if ( $updated > $published ) {
+ return \gmdate( 'Y-m-d\TH:i:s\Z', $updated );
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the content map for the post.
+ *
+ * @return array The content map for the post.
+ */
+ public function get_content_map() {
+ return array(
+ $this->get_locale() => $this->get_content(),
+ );
+ }
+
+ /**
+ * Returns the name map for the post.
+ *
+ * @return array The name map for the post.
+ */
+ public function get_name_map() {
+ if ( ! $this->get_name() ) {
+ return null;
+ }
+
+ return array(
+ $this->get_locale() => $this->get_name(),
+ );
+ }
+
+ /**
+ * Returns the summary map for the post.
+ *
+ * @return array The summary map for the post.
+ */
+ public function get_summary_map() {
+ if ( ! $this->get_summary() ) {
+ return null;
+ }
+
+ return array(
+ $this->get_locale() => $this->get_summary(),
+ );
+ }
+
/**
* Transform Embed blocks to block level link.
*
@@ -870,8 +1036,8 @@ class Post extends Base {
* @see https://www.w3.org/TR/activitypub/#security-sanitizing-content
* @see https://www.w3.org/wiki/ActivityPub/Primer/HTML
*
- * @param string $block_content The block content (html)
- * @param object $block The block object
+ * @param string $block_content The block content (html).
+ * @param object $block The block object.
*
* @return string A block level link
*/
diff --git a/wp-content/plugins/activitypub/integration/class-buddypress.php b/wp-content/plugins/activitypub/integration/class-buddypress.php
index 8d71c1d2..1e86ee5f 100644
--- a/wp-content/plugins/activitypub/integration/class-buddypress.php
+++ b/wp-content/plugins/activitypub/integration/class-buddypress.php
@@ -1,36 +1,50 @@
url = bp_core_get_user_domain( $author_id ); //add BP member profile URL as user URL
+ /**
+ * Add BuddyPress user metadata to the author array.
+ *
+ * @param object $author The author object.
+ * @param int $author_id The author ID.
+ *
+ * @return object The author object.
+ */
+ public static function add_user_metadata( $author, $author_id ) {
+ $author->url = bp_core_get_user_domain( $author_id ); // Add BP member profile URL as user URL.
- // add BuddyPress' cover_image instead of WordPress' header_image
+ // Add BuddyPress' cover_image instead of WordPress' header_image.
$cover_image_url = bp_attachments_get_attachment( 'url', array( 'item_id' => $author_id ) );
if ( $cover_image_url ) {
- $object->image = array(
+ $author->image = array(
'type' => 'Image',
'url' => $cover_image_url,
);
}
- // change profile URL to BuddyPress' profile URL
- $object->attachment['profile_url'] = array(
- 'type' => 'PropertyValue',
- 'name' => \__( 'Profile', 'activitypub' ),
+ // Change profile URL to BuddyPress' profile URL.
+ $author->attachment['profile_url'] = array(
+ 'type' => 'PropertyValue',
+ 'name' => \__( 'Profile', 'activitypub' ),
'value' => \html_entity_decode(
sprintf(
'%s',
@@ -43,18 +57,18 @@ class Buddypress {
),
);
- // replace blog URL on multisite
+ // Replace blog URL on multisite.
if ( is_multisite() ) {
- $user_blogs = get_blogs_of_user( $author_id ); //get sites of user to send as AP metadata
+ $user_blogs = get_blogs_of_user( $author_id ); // Get sites of user to send as AP metadata.
if ( ! empty( $user_blogs ) ) {
- unset( $object->attachment['blog_url'] );
+ unset( $author->attachment['blog_url'] );
foreach ( $user_blogs as $blog ) {
if ( 1 !== $blog->userblog_id ) {
- $object->attachment[] = array(
- 'type' => 'PropertyValue',
- 'name' => $blog->blogname,
+ $author->attachment[] = array(
+ 'type' => 'PropertyValue',
+ 'name' => $blog->blogname,
'value' => \html_entity_decode(
sprintf(
'%s',
@@ -71,6 +85,6 @@ class Buddypress {
}
}
- return $object;
+ return $author;
}
}
diff --git a/wp-content/plugins/activitypub/integration/class-enable-mastodon-apps.php b/wp-content/plugins/activitypub/integration/class-enable-mastodon-apps.php
index 6c643b10..04c24ff7 100644
--- a/wp-content/plugins/activitypub/integration/class-enable-mastodon-apps.php
+++ b/wp-content/plugins/activitypub/integration/class-enable-mastodon-apps.php
@@ -1,4 +1,10 @@
update_icon( $data['avatar'] ) ) {
+ // Unset the avatar so it doesn't get saved again by other plugins.
+ // Ditto for all other fields below.
+ unset( $data['avatar'] );
+ }
+
+ if ( isset( $data['header'] ) && $user->update_header( $data['header'] ) ) {
+ unset( $data['header'] );
+ }
+
+ if ( isset( $data['display_name'] ) && $user->update_name( $data['display_name'] ) ) {
+ unset( $data['display_name'] );
+ }
+
+ if ( isset( $data['note'] ) && $user->update_summary( $data['note'] ) ) {
+ unset( $data['note'] );
+ }
+
+ if ( isset( $data['fields_attributes'] ) ) {
+ self::set_extra_fields( $user_id, $data['fields_attributes'] );
+ unset( $data['fields_attributes'] );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get extra fields for Mastodon API.
+ *
+ * @param int $user_id The user id to act on.
+ * @return array The extra fields.
+ */
+ private static function get_extra_fields( $user_id ) {
+ $ret = array();
+ $fields = Extra_Fields::get_actor_fields( $user_id );
+
+ foreach ( $fields as $field ) {
+ $ret[] = array(
+ 'name' => $field->post_title,
+ 'value' => Extra_Fields::get_formatted_content( $field ),
+ );
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Set extra fields for Mastodon API.
+ *
+ * @param int $user_id The user id to act on.
+ * @param array $fields The fields to set. It is assumed to be the entire set of desired fields.
+ */
+ private static function set_extra_fields( $user_id, $fields ) {
+ // The Mastodon API submits a simple hash for every field.
+ // We can reasonably assume a similar order for our operations below.
+ $ids = wp_list_pluck( Extra_Fields::get_actor_fields( $user_id ), 'ID' );
+ $is_blog = Users::BLOG_USER_ID === $user_id;
+ $post_type = $is_blog ? Extra_Fields::BLOG_POST_TYPE : Extra_Fields::USER_POST_TYPE;
+
+ foreach ( $fields as $i => $field ) {
+ $post_id = $ids[ $i ] ?? null;
+ $has_post = $post_id && \get_post( $post_id );
+ $args = array(
+ 'post_title' => $field['name'],
+ 'post_content' => Extra_Fields::make_paragraph_block( $field['value'] ),
+ );
+
+ if ( $has_post ) {
+ $args['ID'] = $ids[ $i ];
+ \wp_update_post( $args );
+ } else {
+ $args['post_type'] = $post_type;
+ $args['post_status'] = 'publish';
+ if ( ! $is_blog ) {
+ $args['post_author'] = $user_id;
+ }
+ \wp_insert_post( $args );
+ }
+ }
+
+ // Delete any remaining fields.
+ if ( \count( $fields ) < \count( $ids ) ) {
+ $to_delete = \array_slice( $ids, \count( $fields ) );
+ foreach ( $to_delete as $id ) {
+ \wp_delete_post( $id, true );
+ }
+ }
+ }
+
+ /**
+ * Add followers to Mastodon API.
+ *
+ * @param array $followers An array of followers.
+ * @param string $user_id The user id.
*
* @return array The filtered followers
*/
public static function api_account_followers( $followers, $user_id ) {
+ $user_id = self::maybe_map_user_to_blog( $user_id );
$activitypub_followers = Followers::get_followers( $user_id, 40 );
$mastodon_followers = array_map(
function ( $item ) {
@@ -57,92 +193,45 @@ class Enable_Mastodon_Apps {
$acct = $item->get_url();
}
- $account = new Account();
- $account->id = \strval( $item->get__id() );
- $account->username = $item->get_preferred_username();
- $account->acct = $acct;
- $account->display_name = $item->get_name();
- $account->url = $item->get_url();
- $account->uri = $item->get_id();
- $account->avatar = $item->get_icon_url();
- $account->avatar_static = $item->get_icon_url();
- $account->created_at = new DateTime( $item->get_published() );
- $account->last_status_at = new DateTime( $item->get_published() );
- $account->note = $item->get_summary();
- $account->header = $item->get_image_url();
- $account->header_static = $item->get_image_url();
+ $account = new Account();
+ $account->id = \strval( $item->get__id() );
+ $account->username = $item->get_preferred_username();
+ $account->acct = $acct;
+ $account->display_name = $item->get_name();
+ $account->url = $item->get_url();
+ $account->avatar = $item->get_icon_url();
+ $account->avatar_static = $item->get_icon_url();
+ $account->created_at = new DateTime( $item->get_published() );
+ $account->last_status_at = new DateTime( $item->get_published() );
+ $account->note = $item->get_summary();
+ $account->header = $item->get_image_url();
+ $account->header_static = $item->get_image_url();
$account->followers_count = 0;
$account->following_count = 0;
- $account->statuses_count = 0;
- $account->bot = false;
- $account->locked = false;
- $account->group = false;
- $account->discoversable = false;
- $account->indexable = false;
- $account->hide_collections = false;
- $account->noindex = false;
- $account->fields = array();
- $account->emojis = array();
- $account->roles = array();
+ $account->statuses_count = 0;
+ $account->bot = false;
+ $account->locked = false;
+ $account->group = false;
+ $account->discoverable = false;
+ $account->noindex = false;
+ $account->fields = array();
+ $account->emojis = array();
return $account;
},
$activitypub_followers
);
- $followers = array_merge( $mastodon_followers, $followers );
-
- return $followers;
- }
-
- /**
- * Add followers count to Mastodon API
- *
- * @param Enable_Mastodon_Apps\Entity\Account $account The account
- * @param int $user_id The user id
- *
- * @return Enable_Mastodon_Apps\Entity\Account The filtered Account
- */
- public static function api_account_add_followers( $account, $user_id ) {
- if ( ! $account instanceof Account ) {
- return $account;
- }
-
- $user = Users::get_by_various( $user_id );
-
- if ( ! $user || is_wp_error( $user ) ) {
- return $account;
- }
-
- $header = $user->get_image();
- if ( $header ) {
- $account->header = $header['url'];
- $account->header_static = $header['url'];
- }
-
- foreach ( $user->get_attachment() as $attachment ) {
- if ( 'PropertyValue' === $attachment['type'] ) {
- $account->fields[] = array(
- 'name' => $attachment['name'],
- 'value' => $attachment['value'],
- );
- }
- }
-
- $account->acct = $user->get_preferred_username();
- $account->note = $user->get_summary();
-
- $account->followers_count = Followers::count_followers( $user->get__id() );
- return $account;
+ return array_merge( $mastodon_followers, $followers );
}
/**
* Resolve external accounts for Mastodon API
*
- * @param Enable_Mastodon_Apps\Entity\Account $user_data The user data
- * @param string $user_id The user id
+ * @param Account $user_data The user data.
+ * @param string $user_id The user id.
*
- * @return Enable_Mastodon_Apps\Entity\Account The filtered Account
+ * @return Account The filtered Account.
*/
public static function api_account_external( $user_data, $user_id ) {
if ( $user_data || ( is_numeric( $user_id ) && $user_id ) ) {
@@ -170,6 +259,78 @@ class Enable_Mastodon_Apps {
return $user_data;
}
+ /**
+ * Resolve internal accounts for Mastodon API
+ *
+ * @param Account $user_data The user data.
+ * @param string $user_id The user id.
+ *
+ * @return Account The filtered Account.
+ */
+ public static function api_account_internal( $user_data, $user_id ) {
+ $user_id_to_use = self::maybe_map_user_to_blog( $user_id );
+ $user = Users::get_by_id( $user_id_to_use );
+
+ if ( ! $user || is_wp_error( $user ) ) {
+ return $user_data;
+ }
+
+ // Convert user to account.
+ $account = new Account();
+ // Even if we have a blog user, maintain the provided user_id so as not to confuse clients.
+ $account->id = (int) $user_id;
+ $account->username = $user->get_preferred_username();
+ $account->acct = $account->username;
+ $account->display_name = $user->get_name();
+ $account->note = $user->get_summary();
+ $account->source['note'] = wp_strip_all_tags( $account->note, true );
+ $account->url = $user->get_url();
+
+ $icon = $user->get_icon();
+ $account->avatar = $icon['url'];
+ $account->avatar_static = $account->avatar;
+
+ $header = $user->get_image();
+ if ( $header ) {
+ $account->header = $header['url'];
+ $account->header_static = $account->header;
+ }
+
+ $account->created_at = new DateTime( $user->get_published() );
+
+ $post_types = \get_option( 'activitypub_support_post_types', array( 'post' ) );
+ $query_args = array(
+ 'post_type' => $post_types,
+ 'posts_per_page' => 1,
+ );
+ if ( $user_id > 0 ) {
+ $query_args['author'] = $user_id;
+ }
+ $posts = \get_posts( $query_args );
+ $account->last_status_at = ! empty( $posts ) ? new DateTime( $posts[0]->post_date_gmt ) : $account->created_at;
+
+ $account->fields = self::get_extra_fields( $user_id_to_use );
+ // Now do it in source['fields'] with stripped tags.
+ $account->source['fields'] = \array_map(
+ function ( $field ) {
+ $field['value'] = \wp_strip_all_tags( $field['value'], true );
+ return $field;
+ },
+ $account->fields
+ );
+
+ $account->followers_count = Followers::count_followers( $user->get__id() );
+
+ return $account;
+ }
+
+ /**
+ * Get account for actor.
+ *
+ * @param string $uri The URI.
+ *
+ * @return Account|null The account.
+ */
private static function get_account_for_actor( $uri ) {
if ( ! is_string( $uri ) ) {
return null;
@@ -186,11 +347,11 @@ class Enable_Mastodon_Apps {
$acct = substr( $acct, 5 );
}
- $account->id = $acct;
- $account->username = $acct;
- $account->acct = $acct;
- $account->display_name = $data['name'];
- $account->url = $uri;
+ $account->id = $acct;
+ $account->username = $acct;
+ $account->acct = $acct;
+ $account->display_name = $data['name'];
+ $account->url = $uri;
if ( ! empty( $data['summary'] ) ) {
$account->note = $data['summary'];
@@ -217,6 +378,14 @@ class Enable_Mastodon_Apps {
return $account;
}
+ /**
+ * Search by URL for Mastodon API.
+ *
+ * @param array $search_data The search data.
+ * @param object $request The request object.
+ *
+ * @return array The filtered search data.
+ */
public static function api_search_by_url( $search_data, $request ) {
$p = \wp_parse_url( $request->get_param( 'q' ) );
if ( ! $p || ! isset( $p['host'] ) ) {
@@ -241,6 +410,14 @@ class Enable_Mastodon_Apps {
return $search_data;
}
+ /**
+ * Search for Mastodon API.
+ *
+ * @param array $search_data The search data.
+ * @param object $request The request object.
+ *
+ * @return array The filtered search data.
+ */
public static function api_search( $search_data, $request ) {
$user_id = \get_current_user_id();
if ( ! $user_id ) {
@@ -267,20 +444,20 @@ class Enable_Mastodon_Apps {
$acct = $follower->get_url();
}
- $account = new Account();
- $account->id = \strval( $follower->get__id() );
- $account->username = $follower->get_preferred_username();
- $account->acct = $acct;
- $account->display_name = $follower->get_name();
- $account->url = $follower->get_url();
- $account->uri = $follower->get_id();
- $account->avatar = $follower->get_icon_url();
- $account->avatar_static = $follower->get_icon_url();
- $account->created_at = new DateTime( $follower->get_published() );
+ $account = new Account();
+ $account->id = \strval( $follower->get__id() );
+ $account->username = $follower->get_preferred_username();
+ $account->acct = $acct;
+ $account->display_name = $follower->get_name();
+ $account->url = $follower->get_url();
+ $account->uri = $follower->get_id();
+ $account->avatar = $follower->get_icon_url();
+ $account->avatar_static = $follower->get_icon_url();
+ $account->created_at = new DateTime( $follower->get_published() );
$account->last_status_at = new DateTime( $follower->get_published() );
- $account->note = $follower->get_summary();
- $account->header = $follower->get_image_url();
- $account->header_static = $follower->get_image_url();
+ $account->note = $follower->get_summary();
+ $account->header = $follower->get_image_url();
+ $account->header_static = $follower->get_image_url();
$search_data['accounts'][] = $account;
}
@@ -288,6 +465,13 @@ class Enable_Mastodon_Apps {
return $search_data;
}
+ /**
+ * Get posts query args for Mastodon API.
+ *
+ * @param array $args The query arguments.
+ *
+ * @return array The filtered args.
+ */
public static function api_get_posts_query_args( $args ) {
if ( isset( $args['author'] ) && is_string( $args['author'] ) ) {
$uri = Webfinger_Util::resolve( $args['author'] );
@@ -300,6 +484,14 @@ class Enable_Mastodon_Apps {
return $args;
}
+ /**
+ * Convert an activity to a status.
+ *
+ * @param array $item The activity.
+ * @param Account $account The account.
+ *
+ * @return Status|null The status.
+ */
private static function activity_to_status( $item, $account ) {
if ( isset( $item['object'] ) ) {
$object = $item['object'];
@@ -311,7 +503,7 @@ class Enable_Mastodon_Apps {
return null;
}
- $status = new Status();
+ $status = new Status();
$status->id = $object['id'];
$status->created_at = new DateTime( $object['published'] );
$status->content = $object['content'];
@@ -335,20 +527,20 @@ class Enable_Mastodon_Apps {
$status->media_attachments = array_map(
function ( $attachment ) {
$default_attachment = array(
- 'url' => null,
+ 'url' => null,
'mediaType' => null,
- 'name' => null,
- 'width' => 0,
- 'height' => 0,
- 'blurhash' => null,
+ 'name' => null,
+ 'width' => 0,
+ 'height' => 0,
+ 'blurhash' => null,
);
$attachment = array_merge( $default_attachment, $attachment );
- $media_attachment = new Media_Attachment();
- $media_attachment->id = $attachment['url'];
- $media_attachment->type = strtok( $attachment['mediaType'], '/' );
- $media_attachment->url = $attachment['url'];
+ $media_attachment = new Media_Attachment();
+ $media_attachment->id = $attachment['url'];
+ $media_attachment->type = strtok( $attachment['mediaType'], '/' );
+ $media_attachment->url = $attachment['url'];
$media_attachment->preview_url = $attachment['url'];
$media_attachment->description = $attachment['name'];
if ( $attachment['blurhash'] ) {
@@ -372,6 +564,14 @@ class Enable_Mastodon_Apps {
return $status;
}
+ /**
+ * Get posts for Mastodon API.
+ *
+ * @param array $statuses The statuses.
+ * @param array $args The arguments.
+ *
+ * @return array The filtered statuses.
+ */
public static function api_statuses_external( $statuses, $args ) {
if ( ! isset( $args['activitypub'] ) ) {
return $statuses;
@@ -400,8 +600,8 @@ class Enable_Mastodon_Apps {
$limit = 40;
}
$activitypub_statuses = array();
- $url = $outbox['first'];
- $tries = 0;
+ $url = $outbox['first'];
+ $tries = 0;
while ( $url ) {
if ( ++$tries > 3 ) {
break;
@@ -412,7 +612,7 @@ class Enable_Mastodon_Apps {
return $statuses;
}
- $new_statuses = array_map(
+ $new_statuses = array_map(
function ( $item ) use ( $account, $args ) {
if ( $args['exclude_replies'] ) {
if ( isset( $item['object']['inReplyTo'] ) && $item['object']['inReplyTo'] ) {
@@ -424,7 +624,7 @@ class Enable_Mastodon_Apps {
$posts['orderedItems']
);
$activitypub_statuses = array_merge( $activitypub_statuses, array_filter( $new_statuses ) );
- $url = $posts['next'];
+ $url = $posts['next'];
if ( count( $activitypub_statuses ) >= $limit ) {
break;
@@ -434,6 +634,15 @@ class Enable_Mastodon_Apps {
return array_slice( $activitypub_statuses, 0, $limit );
}
+ /**
+ * Get replies for Mastodon API.
+ *
+ * @param array $context The context.
+ * @param int $post_id The post id.
+ * @param string $url The URL.
+ *
+ * @return array The filtered context.
+ */
public static function api_get_replies( $context, $post_id, $url ) {
$meta = Http::get_remote_object( $url, true );
if ( is_wp_error( $meta ) || ! isset( $meta['replies']['first']['next'] ) ) {
@@ -441,7 +650,7 @@ class Enable_Mastodon_Apps {
}
$replies_url = $meta['replies']['first']['next'];
- $replies = Http::get_remote_object( $replies_url, true );
+ $replies = Http::get_remote_object( $replies_url, true );
if ( is_wp_error( $replies ) || ! isset( $replies['items'] ) ) {
return $context;
}
@@ -457,7 +666,7 @@ class Enable_Mastodon_Apps {
}
$account = self::get_account_for_actor( $status['attributedTo'] );
- $status = self::activity_to_status( $status, $account );
+ $status = self::activity_to_status( $status, $account );
if ( $status ) {
$context['descendants'][ $status->id ] = $status;
}
diff --git a/wp-content/plugins/activitypub/integration/class-jetpack.php b/wp-content/plugins/activitypub/integration/class-jetpack.php
index 009811e9..002de2cb 100644
--- a/wp-content/plugins/activitypub/integration/class-jetpack.php
+++ b/wp-content/plugins/activitypub/integration/class-jetpack.php
@@ -1,21 +1,40 @@
= '2.0' ) {
@@ -47,11 +53,11 @@ class Nodeinfo {
}
/**
- * Extend NodeInfo2 data
+ * Extend NodeInfo2 data.
*
- * @param array $nodeinfo NodeInfo2 data
+ * @param array $nodeinfo NodeInfo2 data.
*
- * @return array The extended array
+ * @return array The extended array.
*/
public static function add_nodeinfo2_data( $nodeinfo ) {
$nodeinfo['protocols'][] = 'activitypub';
@@ -66,15 +72,15 @@ class Nodeinfo {
}
/**
- * Extend the well-known nodeinfo data
+ * Extend the well-known nodeinfo data.
*
- * @param array $data The well-known nodeinfo data
+ * @param array $data The well-known nodeinfo data.
*
- * @return array The extended array
+ * @return array The extended array.
*/
public static function add_wellknown_nodeinfo_data( $data ) {
$data['links'][] = array(
- 'rel' => 'https://www.w3.org/ns/activitystreams#Application',
+ 'rel' => 'https://www.w3.org/ns/activitystreams#Application',
'href' => get_rest_url_by_path( 'application' ),
);
diff --git a/wp-content/plugins/activitypub/integration/class-opengraph.php b/wp-content/plugins/activitypub/integration/class-opengraph.php
index 2d0f8c96..4eb1b1e0 100644
--- a/wp-content/plugins/activitypub/integration/class-opengraph.php
+++ b/wp-content/plugins/activitypub/integration/class-opengraph.php
@@ -1,4 +1,10 @@
get_webfinger();
return $metadata;
}
if ( \is_author() ) {
- // Use the Author of the Archive-Page
+ // Use the Author of the Archive-Page.
$user_id = \get_queried_object_id();
} elseif ( \is_singular() ) {
- // Use the Author of the Post
+ // Use the Author of the Post.
$user_id = \get_post_field( 'post_author', \get_queried_object_id() );
} elseif ( ! is_user_type_disabled( 'blog' ) ) {
- // Use the Blog-User for any other page, if the Blog-User is not disabled
+ // Use the Blog-User for any other page, if the Blog-User is not disabled.
$user_id = Users::BLOG_USER_ID;
} else {
- // Do not add any metadata otherwise
+ // Do not add any metadata otherwise.
return $metadata;
}
@@ -78,7 +84,7 @@ class Opengraph {
return $metadata;
}
- // add WebFinger resource
+ // Add WebFinger resource.
$metadata['fediverse:creator'] = $user->get_webfinger();
return $metadata;
diff --git a/wp-content/plugins/activitypub/integration/class-seriously-simple-podcasting.php b/wp-content/plugins/activitypub/integration/class-seriously-simple-podcasting.php
new file mode 100644
index 00000000..c678f6d3
--- /dev/null
+++ b/wp-content/plugins/activitypub/integration/class-seriously-simple-podcasting.php
@@ -0,0 +1,68 @@
+wp_object;
+ $attachments = parent::get_attachment();
+
+ $attachment = array(
+ 'type' => \esc_attr( \get_post_meta( $post->ID, 'episode_type', true ) ),
+ 'url' => \esc_url( \get_post_meta( $post->ID, 'audio_file', true ) ),
+ 'name' => \esc_attr( \get_the_title( $post->ID ) ),
+ 'icon' => \esc_url( \get_post_meta( $post->ID, 'cover_image', true ) ),
+ );
+
+ $attachment = array_filter( $attachment );
+ array_unshift( $attachments, $attachment );
+
+ return $attachments;
+ }
+
+ /**
+ * Gets the object type for a podcast episode.
+ *
+ * Always returns 'Note' for the best possible compatibility with ActivityPub.
+ *
+ * @return string The object type.
+ */
+ public function get_type() {
+ return 'Note';
+ }
+
+ /**
+ * Returns the content for the ActivityPub Item.
+ *
+ * The content will be generated based on the user settings.
+ *
+ * @return string The content.
+ */
+ public function get_content() {
+ return generate_post_summary( $this->wp_object );
+ }
+}
diff --git a/wp-content/plugins/activitypub/integration/class-stream-connector.php b/wp-content/plugins/activitypub/integration/class-stream-connector.php
new file mode 100644
index 00000000..18ffb5da
--- /dev/null
+++ b/wp-content/plugins/activitypub/integration/class-stream-connector.php
@@ -0,0 +1,82 @@
+log(
+ sprintf(
+ // translators: %s is a URL.
+ __( 'New Follower: %s', 'activitypub' ),
+ $notification->actor
+ ),
+ array(
+ 'notification' => \wp_json_encode( $notification, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ),
+ ),
+ null,
+ 'notification',
+ $notification->type,
+ $notification->target
+ );
+ }
+}
diff --git a/wp-content/plugins/activitypub/integration/class-webfinger.php b/wp-content/plugins/activitypub/integration/class-webfinger.php
index c9727861..7ae9607d 100644
--- a/wp-content/plugins/activitypub/integration/class-webfinger.php
+++ b/wp-content/plugins/activitypub/integration/class-webfinger.php
@@ -1,9 +1,16 @@
ID );
if ( ! $user || is_wp_error( $user ) ) {
- return $array;
+ return $jrd;
}
- $array['subject'] = sprintf( 'acct:%s', $user->get_webfinger() );
+ $jrd['subject'] = sprintf( 'acct:%s', $user->get_webfinger() );
- $array['aliases'][] = $user->get_url();
- $array['aliases'][] = $user->get_alternate_url();
+ $jrd['aliases'][] = $user->get_url();
+ $jrd['aliases'][] = $user->get_alternate_url();
- $array['links'][] = array(
+ $jrd['links'][] = array(
'rel' => 'self',
'type' => 'application/activity+json',
'href' => $user->get_url(),
);
- return $array;
+ $jrd['links'][] = array(
+ 'rel' => 'http://ostatus.org/schema/1.0/subscribe',
+ 'template' => get_rest_url_by_path( 'interactions?uri={uri}' ),
+ );
+
+ return $jrd;
}
/**
- * Add WebFinger discovery links
+ * Add WebFinger discovery links.
*
- * @param array $array the jrd array
- * @param string $resource the WebFinger resource
- * @param WP_User $user the WordPress user
+ * @param array $jrd The jrd array.
+ * @param string $uri The WebFinger resource.
*
- * @return array the jrd array
+ * @return array|\WP_Error The jrd array or WP_Error.
*/
- public static function add_pseudo_user_discovery( $array, $resource ) {
- $user = Webfinger_Rest::get_profile( $resource );
+ public static function add_pseudo_user_discovery( $jrd, $uri ) {
+ $user = User_Collection::get_by_resource( $uri );
- if ( ! $user || is_wp_error( $user ) ) {
- return $array;
+ if ( \is_wp_error( $user ) ) {
+ return $user;
}
- return $user;
+ $aliases = array(
+ $user->get_url(),
+ $user->get_alternate_url(),
+ );
+
+ $aliases = array_unique( $aliases );
+
+ $profile = array(
+ 'subject' => sprintf( 'acct:%s', $user->get_webfinger() ),
+ 'aliases' => array_values( array_unique( $aliases ) ),
+ 'links' => array(
+ array(
+ 'rel' => 'self',
+ 'type' => 'application/activity+json',
+ 'href' => $user->get_url(),
+ ),
+ array(
+ 'rel' => 'http://webfinger.net/rel/profile-page',
+ 'type' => 'text/html',
+ 'href' => $user->get_url(),
+ ),
+ array(
+ 'rel' => 'http://ostatus.org/schema/1.0/subscribe',
+ 'template' => get_rest_url_by_path( 'interactions?uri={uri}' ),
+ ),
+ ),
+ );
+
+ if ( 'Person' !== $user->get_type() ) {
+ $profile['links'][0]['properties'] = array(
+ 'https://www.w3.org/ns/activitystreams#type' => $user->get_type(),
+ );
+ }
+
+ return $profile;
}
}
diff --git a/wp-content/plugins/activitypub/integration/load.php b/wp-content/plugins/activitypub/integration/load.php
new file mode 100644
index 00000000..860f2dc7
--- /dev/null
+++ b/wp-content/plugins/activitypub/integration/load.php
@@ -0,0 +1,155 @@
+ID, 'audio_file', true )
+ ) {
+ require_once __DIR__ . '/class-seriously-simple-podcasting.php';
+ return new Seriously_Simple_Podcasting( $data );
+ }
+ return $transformer;
+ },
+ 10,
+ 3
+ );
+ }
+}
+\add_action( 'plugins_loaded', __NAMESPACE__ . '\plugin_init' );
+
+/**
+ * Register the Stream Connector for ActivityPub.
+ *
+ * @param array $classes The Stream connectors.
+ *
+ * @return array The Stream connectors with the ActivityPub connector.
+ */
+function register_stream_connector( $classes ) {
+ require plugin_dir_path( __FILE__ ) . '/class-stream-connector.php';
+
+ $class_name = '\Activitypub\Integration\Stream_Connector';
+
+ if ( ! class_exists( $class_name ) ) {
+ return;
+ }
+
+ wp_stream_get_instance();
+ $class = new $class_name();
+
+ if ( ! method_exists( $class, 'is_dependency_satisfied' ) ) {
+ return;
+ }
+
+ if ( $class->is_dependency_satisfied() ) {
+ $classes[] = $class;
+ }
+
+ return $classes;
+}
+add_filter( 'wp_stream_connectors', __NAMESPACE__ . '\register_stream_connector' );
+
+// Excluded ActivityPub post types from the Stream.
+add_filter(
+ 'wp_stream_posts_exclude_post_types',
+ function ( $post_types ) {
+ $post_types[] = 'ap_follower';
+ $post_types[] = 'ap_extrafield';
+ $post_types[] = 'ap_extrafield_blog';
+ return $post_types;
+ }
+);
+
+/**
+ * Load the BuddyPress integration.
+ *
+ * Only load code that needs BuddyPress to run once BP is loaded and initialized.
+ *
+ * @see https://buddypress.org/
+ */
+add_action(
+ 'bp_include',
+ function () {
+ require_once __DIR__ . '/class-buddypress.php';
+ Buddypress::init();
+ },
+ 0
+);
diff --git a/wp-content/plugins/activitypub/readme.txt b/wp-content/plugins/activitypub/readme.txt
index 3dcd963b..ebe777b0 100644
--- a/wp-content/plugins/activitypub/readme.txt
+++ b/wp-content/plugins/activitypub/readme.txt
@@ -3,7 +3,7 @@ Contributors: automattic, pfefferle, mediaformat, mattwiebe, akirk, jeherve, nur
Tags: OStatus, fediverse, activitypub, activitystream
Requires at least: 5.5
Tested up to: 6.6
-Stable tag: 2.6.1
+Stable tag: 3.3.3
Requires PHP: 7.0
License: MIT
License URI: http://opensource.org/licenses/MIT
@@ -20,7 +20,7 @@ An example: I give you my Mastodon profile name: `@pfefferle@mastodon.social`. Y
Once you follow Jane's `@jane@example.com` profile, any blog post she crafts on `example.com` will land in your Home feed. Simultaneously, by following the blog-wide profile `@example.com@example.com`, you'll receive updates from all authors.
-**Note**: if no one follows your author or blog instance, your posts remain unseen. The simplest method to verify the plugin's operation is by following your profile. If you possess a Mastodon profile, initiate by following your new one.
+**Note**: If no one follows your author or blog instance, your posts remain unseen. The simplest method to verify the plugin's operation is by following your profile. If you possess a Mastodon profile, initiate by following your new one.
The plugin works with the following tested federated platforms, but there may be more that it works with as well:
@@ -48,7 +48,7 @@ So what’s the process?
1. On your blog, publish a new post.
1. From Mastodon, check to see if the new post appears in your Home feed.
-Please note that it may take up to 15 minutes or so for the new post to show up in your federated feed. This is because the messages are sent to the federated platforms using a delayed cron. This avoids breaking the publishing process for those cases where users might have lots of followers. So please don’t assume that just because you didn’t see it show up right away that something is broken. Give it some time. In most cases, it will show up within a few minutes, and you’ll know everything is working as expected.
+**Note**: It may take up to 15 minutes or so for the new post to show up in your federated feed. This is because the messages are sent to the federated platforms using a delayed cron. This avoids breaking the publishing process for those cases where users might have lots of followers. So please don’t assume that just because you didn’t see it show up right away that something is broken. Give it some time. In most cases, it will show up within a few minutes, and you’ll know everything is working as expected.
== Frequently Asked Questions ==
@@ -105,6 +105,12 @@ Where 'blog' is the path to the subdirectory at which your blog resides.
If you are running your blog in a subdirectory, but have a different [wp_siteurl](https://wordpress.org/documentation/article/giving-wordpress-its-own-directory/), you don't need the redirect, because the index.php will take care of that.
+= What if you are running your blog behind a reverse proxy with Apache? =
+
+If you are using a reverse proxy with Apache to run your host you may encounter that you are unable to have followers join the blog. This will occur because the proxy system rewrites the host headers to be the internal DNS name of your server, which the plugin then uses to attempt to sign the replies. The remote site attempting to follow your users is expecting the public DNS name on the replies. In these cases you will need to use the 'ProxyPreserveHost On' directive to ensure the external host name is passed to your internal host.
+
+If you are using SSL between the proxy and internal host you may also need to `SSLProxyCheckPeerName off` if your internal host can not answer with the correct SSL name. This may present a security issue in some environments.
+
= Constants =
The plugin uses PHP Constants to enable, disable or change its default behaviour. Please use them with caution and only if you know what you are doing.
@@ -115,6 +121,7 @@ The plugin uses PHP Constants to enable, disable or change its default behaviour
* `ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS` - Change the number of attachments, that should be federated. Default: `3`.
* `ACTIVITYPUB_HASHTAGS_REGEXP` - Change the default regex to detect hashtext in a text. Default: `(?:(?<=\s)|(?<=
)|(?<= )|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))`.
* `ACTIVITYPUB_USERNAME_REGEXP` - Change the default regex to detect @-replies in a text. Default: `(?:([A-Za-z0-9\._-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))`.
+* `ACTIVITYPUB_URL_REGEXP` - Change the default regex to detect urls in a text. Default: `(www.|http:|https:)+[^\s]+[\w\/]`.
* `ACTIVITYPUB_CUSTOM_POST_CONTENT` - Change the default template for Activities. Default: `[ap_title]\n\n[ap_content]\n\n[ap_hashtags]\n\n[ap_shortlink]`.
* `ACTIVITYPUB_AUTHORIZED_FETCH` - Enable AUTHORIZED_FETCH. Default: `false`.
* `ACTIVITYPUB_DISABLE_REWRITES` - Disable auto generation of `mod_rewrite` rules. Default: `false`.
@@ -131,62 +138,95 @@ The followers of a user can be found in the menu under "Users" -> "Followers" or
For reasons of data protection, it is not possible to see the followers of other users.
+== Screenshots ==
+
+1. The "Follow me"-Block in the Block-Editor
+2. The "Followers"-Block in the Block-Editor
+3. The "Federated Reply"-Block in the Block-Editor
+4. A "Federated Reply" in a Post
+5. A Blog-Profile on Mastodon
+
== Changelog ==
-= 2.6.1 =
+= 3.3.3 =
-* Fixed: Extra Fields will generate wrong entries
+* Fixed: Sanitization callback
+* Improved: A lot of PHPCS cleanups
+* Improved: Prepare multi-lang support
-= 2.6.0 =
+= 3.3.2 =
-* Added: Support for FEP-fb2a
-* Added: CRUD support for Extra Fields
-* Improved: Remote-Follow UI and UX
-* Improved: Open Graph `fediverse:creator` implementation
-* Fixed: Compatibility issues with fed.brid.gy
-* Fixed: Remote-Reply endpoint
-* Fixed: WebFinger Error Codes (thanks to the FediTest project)
-* Fixed: Fatal Error when wp_schedule_single_event third argument is being passed as a string
+* Fixed: Keep priority of Icons
+* Fixed: Fatal error if remote-object is `WP_Error`
+* Improved: Adopt WordPress PHP Coding Standards
-= 2.5.0 =
+= 3.3.1 =
-* Added: WebFinger cors header
-* Added: WebFinger Content-Type
-* Added: The Fediverse creator of a post to OpenGraph
-* Improved: Try to lookup local users first for Enable Mastodon Apps
-* Improved: Send also Announces for deletes
-* Improved: Load time by adding `count_total=false` to `WP_User_Query`
-* Fixed: Several WebFinger issues
-* Fixed: Redirect issue for Application user
-* Fixed: Accessibilty issues with missing screen-reader-text on User overview page
+* Fixed: PHP Warnings
+* Fixed: PHPCS issues
-= 2.4.0 =
+= 3.3.0 =
-* Added: A core/embed block filter to transform iframes to links
-* Added: Basic support of incoming `Announce`s
-* Added: Improve attachment handling
-* Added: Notifications: Introduce general class and use it for new follows
-* Added: Always fall back to `get_by_username` if one of the above fail
-* Added: Notification support for Jetpack
-* Added: EMA: Support for fetching external statuses without replies
-* Added: EMA: Remote context
-* Added: EMA: Allow searching for URLs
-* Added: EMA: Ensuring numeric ids is now done in EMA directly
-* Added: Podcast support
-* Added: Follower count to "At a Glance" dashboard widget
-* Improved: Use `Note` as default Object-Type, instead of `Article`
-* Improved: Improve `AUTHORIZED_FETCH`
-* Improved: Only send Mentions to comments in the direct hierarchy
-* Improved: Improve transformer
-* Improved: Improve Lemmy compatibility
-* Improved: Updated JS dependencies
-* Fixed: EMA: Add missing static keyword and try to lookup if the id is 0
-* Fixed: Blog-wide account when WordPress is in subdirectory
-* Fixed: Funkwhale URLs
-* Fixed: Prevent infinite loops in `get_comment_ancestors`
-* Fixed: Better Content-Negotiation handling
+* Added: Content warning support
+* Added: Replies collection
+* Added: Enable Mastodon Apps: support profile editing, blog user
+* Added: Follow Me/Followers: add inherit mode for dynamic templating
+* Fixed: Cropping Header Images for users without the 'customize' capability
+* Improved: OpenSSL handling
+* Improved: Added missing @ in Follow-Me block
-See full Changelog on [GitHub](https://github.com/Automattic/wordpress-activitypub/blob/master/CHANGELOG.md).
+= 3.2.5 =
+
+* Fixed: Enable Mastodon Apps check
+* Fixed: Fediverse replies were not threaded properly
+
+= 3.2.4 =
+
+* Improved: Inbox validation
+
+= 3.2.3 =
+
+* Fixed: NodeInfo endpoint
+* Fixed: (Temporarily) Remove HTML from `summary`, because it seems that Mastodon has issues with it
+* Improved: Accessibility for Reply-Context
+* Improved: Use `Article` Object-Type as default
+
+= 3.2.2 =
+
+* Fixed: Extra-Fields check
+
+= 3.2.1 =
+
+* Fixed: Use `Excerpt` for Podcast Episodes
+
+= 3.2.0 =
+
+* Added: Support for Seriously Simple Podcasting
+* Added: Blog extra fields
+* Added: Support "read more" for Activity-Summary
+* Added: `Like` and `Announce` (Boost) handler
+* Added: Simple Remote-Reply endpoint
+* Added: "Stream" Plugin support
+* Added: New Fediverse symbol
+* Improved: Replace hashtags, urls and mentions in summary with links
+* Improved: Hide Bookmarklet if site does not support Blocks
+* Fixed: Link detection for extra fields when spaces after the link and fix when two links in the content
+* Fixed: `Undo` for `Likes` and `Announces`
+* Fixed: Show Avatars on `Likes` and `Announces`
+* Fixed: Remove proprietary WebFinger resource
+* Fixed: Wrong followers URL in "to" attribute of posts
+
+= 3.1.0 =
+
+* Added: `menu_order` to `ap_extrafield` so that user can decide in with order they will be displayed
+* Added: Line brakes to user biography
+* Added: Blueprint
+* Fixed: Changed missing `activitypub_user_description` to `activitypub_description`
+* Fixed: Undefined `get_sample_permalink`
+* Fixed: Only send Update for previously-published posts
+* Improved: Simplified WebFinger code
+
+See full Changelog on [GitHub](https://github.com/Automattic/wordpress-activitypub/blob/trunk/CHANGELOG.md).
== Upgrade Notice ==
diff --git a/wp-content/plugins/activitypub/templates/admin-header.php b/wp-content/plugins/activitypub/templates/admin-header.php
index 67b91ba9..c453328d 100644
--- a/wp-content/plugins/activitypub/templates/admin-header.php
+++ b/wp-content/plugins/activitypub/templates/admin-header.php
@@ -1,5 +1,20 @@
'',
+ 'settings' => '',
+ 'blog-profile' => '',
+ 'followers' => '',
+ )
+);
?>