commit
b9f8294140
22 changed files with 585 additions and 795 deletions
|
@ -112,7 +112,7 @@ Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github
|
|||
|
||||
### 0.14.0 ###
|
||||
|
||||
* Friends support: https://wordpress.org/plugins/friends/ . props [@akirk](https://github.com/akirk)
|
||||
* Friends support: https://wordpress.org/plugins/friends/ props [@akirk](https://github.com/akirk)
|
||||
* Massive guidance improvements. props [mediaformat](https://github.com/mediaformat) & [@akirk](https://github.com/akirk)
|
||||
* Add Custom Post Type support to outbox API. props [blueset](https://github.com/blueset)
|
||||
* Better hash-tag support. props [bocops](https://github.com/bocops)
|
||||
|
|
|
@ -22,6 +22,8 @@ function init() {
|
|||
\defined( 'ACTIVITYPUB_EXCERPT_LENGTH' ) || \define( 'ACTIVITYPUB_EXCERPT_LENGTH', 400 );
|
||||
\defined( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS' ) || \define( 'ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS', 3 );
|
||||
\defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|(?<=<p>)|(?<=<br>)|^)#([A-Za-z0-9_]+)(?:(?=\s|[[:punct:]]|$))' );
|
||||
\defined( 'ACTIVITYPUB_USERNAME_REGEXP' ) || \define( 'ACTIVITYPUB_USERNAME_REGEXP', '(?:([A-Za-z0-9_-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))' );
|
||||
\defined( 'ACTIVITYPUB_ALLOWED_HTML' ) || \define( 'ACTIVITYPUB_ALLOWED_HTML', '<strong><a><p><ul><ol><li><code><blockquote><pre><img>' );
|
||||
\defined( 'ACTIVITYPUB_CUSTOM_POST_CONTENT' ) || \define( 'ACTIVITYPUB_CUSTOM_POST_CONTENT', "<p><strong>[ap_title]</strong></p>\n\n[ap_content]\n\n<p>[ap_hashtags]</p>\n\n<p>[ap_shortlink]</p>" );
|
||||
\define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
\define( 'ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
||||
|
@ -73,6 +75,9 @@ function init() {
|
|||
require_once \dirname( __FILE__ ) . '/includes/class-shortcodes.php';
|
||||
\Activitypub\Shortcodes::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-mention.php';
|
||||
\Activitypub\Mention::init();
|
||||
|
||||
require_once \dirname( __FILE__ ) . '/includes/class-debug.php';
|
||||
\Activitypub\Debug::init();
|
||||
|
||||
|
@ -142,11 +147,3 @@ function enable_buddypress_features() {
|
|||
\Activitypub\Integration\Buddypress::init();
|
||||
}
|
||||
add_action( 'bp_include', '\Activitypub\enable_buddypress_features' );
|
||||
|
||||
add_action(
|
||||
'friends_load_parsers',
|
||||
function( \Friends\Feed $friends_feed ) {
|
||||
require_once __DIR__ . '/integration/class-friends-feed-parser-activitypub.php';
|
||||
$friends_feed->register_parser( Friends_Feed_Parser_ActivityPub::SLUG, new Friends_Feed_Parser_ActivityPub( $friends_feed ) );
|
||||
}
|
||||
);
|
||||
|
|
|
@ -23,16 +23,35 @@ class Activity_Dispatcher {
|
|||
*
|
||||
* @param \Activitypub\Model\Post $activitypub_post
|
||||
*/
|
||||
public static function send_post_activity( $activitypub_post ) {
|
||||
public static function send_post_activity( Model\Post $activitypub_post ) {
|
||||
// get latest version of post
|
||||
$user_id = $activitypub_post->get_post_author();
|
||||
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post->to_array() );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
|
||||
foreach ( \Activitypub\get_follower_inboxes( $user_id ) as $inbox => $to ) {
|
||||
$inboxes = \Activitypub\get_follower_inboxes( $user_id );
|
||||
|
||||
$followers_url = \get_rest_url( null, '/activitypub/1.0/users/' . intval( $user_id ) . '/followers' );
|
||||
foreach ( $activitypub_activity->get_cc() as $cc ) {
|
||||
if ( $cc === $followers_url ) {
|
||||
continue;
|
||||
}
|
||||
$inbox = \Activitypub\get_inbox_by_actor( $cc );
|
||||
if ( ! $inbox || \is_wp_error( $inbox ) ) {
|
||||
continue;
|
||||
}
|
||||
// init array if empty
|
||||
if ( ! isset( $inboxes[ $inbox ] ) ) {
|
||||
$inboxes[ $inbox ] = array();
|
||||
}
|
||||
$inboxes[ $inbox ][] = $cc;
|
||||
}
|
||||
|
||||
foreach ( $inboxes as $inbox => $to ) {
|
||||
$to = array_values( array_unique( $to ) );
|
||||
$activitypub_activity->set_to( $to );
|
||||
$activity = $activitypub_activity->to_json(); // phpcs:ignore
|
||||
$activity = $activitypub_activity->to_json();
|
||||
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
|
|
|
@ -21,15 +21,15 @@ class Hashtag {
|
|||
* Filter to save #tags as real WordPress tags
|
||||
*
|
||||
* @param int $id the rev-id
|
||||
* @param array $data the post-data as array
|
||||
* @param WP_Post $post the post
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static function insert_post( $id, $data ) {
|
||||
if ( \preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', $data->post_content, $match ) ) {
|
||||
public static function insert_post( $id, $post ) {
|
||||
if ( \preg_match_all( '/' . ACTIVITYPUB_HASHTAGS_REGEXP . '/i', $post->post_content, $match ) ) {
|
||||
$tags = \implode( ', ', $match[1] );
|
||||
|
||||
\wp_add_post_tags( $data->post_parent, $tags );
|
||||
\wp_add_post_tags( $post->post_parent, $tags );
|
||||
}
|
||||
|
||||
return $id;
|
||||
|
|
86
includes/class-mention.php
Normal file
86
includes/class-mention.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Mention Class
|
||||
*
|
||||
* @author Alex Kirk
|
||||
*/
|
||||
class Mention {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'the_content', array( '\Activitypub\Mention', 'the_content' ), 99, 2 );
|
||||
\add_filter( 'activitypub_extract_mentions', array( '\Activitypub\Mention', 'extract_mentions' ), 99, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to replace the mentions in the content with links
|
||||
*
|
||||
* @param string $the_content the post-content
|
||||
*
|
||||
* @return string the filtered post-content
|
||||
*/
|
||||
public static function the_content( $the_content ) {
|
||||
$protected_tags = array();
|
||||
$the_content = preg_replace_callback(
|
||||
'#<a.*?href=[^>]+>.*?</a>#i',
|
||||
function( $m ) use ( &$protected_tags ) {
|
||||
$c = count( $protected_tags );
|
||||
$protect = '!#!#PROTECT' . $c . '#!#!';
|
||||
$protected_tags[ $protect ] = $m[0];
|
||||
return $protect;
|
||||
},
|
||||
$the_content
|
||||
);
|
||||
|
||||
$the_content = \preg_replace_callback( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/', array( '\Activitypub\Mention', 'replace_with_links' ), $the_content );
|
||||
|
||||
$the_content = str_replace( array_keys( $protected_tags ), array_values( $protected_tags ), $the_content );
|
||||
|
||||
return $the_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback for preg_replace to build the user links
|
||||
*
|
||||
* @param array $result the preg_match results
|
||||
* @return string the final string
|
||||
*/
|
||||
public static function replace_with_links( $result ) {
|
||||
$metadata = \ActivityPub\get_remote_metadata_by_actor( $result[0] );
|
||||
if ( ! is_wp_error( $metadata ) && ! empty( $metadata['url'] ) ) {
|
||||
$username = ltrim( $result[0], '@' );
|
||||
if ( ! empty( $metadata['name'] ) ) {
|
||||
$username = $metadata['name'];
|
||||
}
|
||||
if ( ! empty( $metadata['preferredUsername'] ) ) {
|
||||
$username = $metadata['preferredUsername'];
|
||||
}
|
||||
$username = '@<span>' . $username . '</span>';
|
||||
return \sprintf( '<a rel="mention" class="u-url mention" href="%s">%s</a>', $metadata['url'], $username );
|
||||
}
|
||||
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the mentions from the post_content.
|
||||
*
|
||||
* @param array $mentions The already found mentions.
|
||||
* @param string $post_content The post content.
|
||||
* @return mixed The discovered mentions.
|
||||
*/
|
||||
public static function extract_mentions( $mentions, $post_content ) {
|
||||
\preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post_content, $matches );
|
||||
foreach ( $matches[0] as $match ) {
|
||||
$link = \Activitypub\Webfinger::resolve( $match );
|
||||
if ( ! is_wp_error( $link ) ) {
|
||||
$mentions[ $match ] = $link;
|
||||
}
|
||||
}
|
||||
return $mentions;
|
||||
|
||||
}
|
||||
}
|
|
@ -28,12 +28,21 @@ class Webfinger {
|
|||
}
|
||||
|
||||
public static function resolve( $account ) {
|
||||
if ( ! preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $account, $m ) ) {
|
||||
if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $account, $m ) ) {
|
||||
return null;
|
||||
}
|
||||
$url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[1] . '/.well-known/webfinger' );
|
||||
$transient_key = 'activitypub_resolve_' . ltrim( $account, '@' );
|
||||
|
||||
$link = \get_transient( $transient_key );
|
||||
if ( $link ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
$url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[2] . '/.well-known/webfinger' );
|
||||
if ( ! \wp_http_validate_url( $url ) ) {
|
||||
return new \WP_Error( 'invalid_webfinger_url', null, $url );
|
||||
$response = new \WP_Error( 'invalid_webfinger_url', null, $url );
|
||||
\set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $response;
|
||||
}
|
||||
|
||||
// try to access author URL
|
||||
|
@ -42,28 +51,34 @@ class Webfinger {
|
|||
array(
|
||||
'headers' => array( 'Accept' => 'application/activity+json' ),
|
||||
'redirection' => 0,
|
||||
'timeout' => 2,
|
||||
)
|
||||
);
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
return new \WP_Error( 'webfinger_url_not_accessible', null, $url );
|
||||
$link = new \WP_Error( 'webfinger_url_not_accessible', null, $url );
|
||||
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $link;
|
||||
}
|
||||
|
||||
$response_code = \wp_remote_retrieve_response_code( $response );
|
||||
|
||||
$body = \wp_remote_retrieve_body( $response );
|
||||
$body = \json_decode( $body, true );
|
||||
|
||||
if ( ! isset( $body['links'] ) ) {
|
||||
return new \WP_Error( 'webfinger_url_invalid_response', null, $url );
|
||||
if ( empty( $body['links'] ) ) {
|
||||
$link = new \WP_Error( 'webfinger_url_invalid_response', null, $url );
|
||||
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $link;
|
||||
}
|
||||
|
||||
foreach ( $body['links'] as $link ) {
|
||||
if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) {
|
||||
\set_transient( $transient_key, $link['href'], WEEK_IN_SECONDS );
|
||||
return $link['href'];
|
||||
}
|
||||
}
|
||||
|
||||
return new \WP_Error( 'webfinger_url_no_activity_pub', null, $body );
|
||||
$link = new \WP_Error( 'webfinger_url_no_activity_pub', null, $body );
|
||||
\set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $link;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ function safe_remote_get( $url, $user_id ) {
|
|||
$wp_version = \get_bloginfo( 'version' );
|
||||
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
|
||||
$args = array(
|
||||
'timeout' => 100,
|
||||
'timeout' => apply_filters( 'activitypub_remote_get_timeout', 100 ),
|
||||
'limit_response_size' => 1048576,
|
||||
'redirection' => 3,
|
||||
'user-agent' => "$user_agent; ActivityPub",
|
||||
|
@ -110,8 +110,8 @@ function get_remote_metadata_by_actor( $actor ) {
|
|||
if ( $pre ) {
|
||||
return $pre;
|
||||
}
|
||||
if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $actor ) ) {
|
||||
$actor = \Activitypub\Webfinger::resolve( $actor );
|
||||
if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $actor ) ) {
|
||||
$actor = Webfinger::resolve( $actor );
|
||||
}
|
||||
|
||||
if ( ! $actor ) {
|
||||
|
@ -122,30 +122,37 @@ function get_remote_metadata_by_actor( $actor ) {
|
|||
return $actor;
|
||||
}
|
||||
|
||||
$metadata = \get_transient( 'activitypub_' . $actor );
|
||||
$transient_key = 'activitypub_' . $actor;
|
||||
$metadata = \get_transient( $transient_key );
|
||||
|
||||
if ( $metadata ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if ( ! \wp_http_validate_url( $actor ) ) {
|
||||
return new \WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), $actor );
|
||||
$metadata = new \WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), $actor );
|
||||
\set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$user = \get_users(
|
||||
array(
|
||||
'number' => 1,
|
||||
'who' => 'authors',
|
||||
'capability__in' => array( 'publish_posts' ),
|
||||
'fields' => 'ID',
|
||||
)
|
||||
);
|
||||
|
||||
// we just need any user to generate a request signature
|
||||
$user_id = \reset( $user );
|
||||
|
||||
$short_timeout = function() {
|
||||
return 3;
|
||||
};
|
||||
add_filter( 'activitypub_remote_get_timeout', $short_timeout );
|
||||
$response = \Activitypub\safe_remote_get( $actor, $user_id );
|
||||
|
||||
remove_filter( 'activitypub_remote_get_timeout', $short_timeout );
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
\set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
@ -153,10 +160,12 @@ function get_remote_metadata_by_actor( $actor ) {
|
|||
$metadata = \json_decode( $metadata, true );
|
||||
|
||||
if ( ! $metadata ) {
|
||||
return new \WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), $actor );
|
||||
$metadata = new \WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), $actor );
|
||||
\set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
\set_transient( 'activitypub_' . $actor, $metadata, WEEK_IN_SECONDS );
|
||||
\set_transient( $transient_key, $metadata, WEEK_IN_SECONDS );
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
|
|
@ -45,20 +45,28 @@ class Activity {
|
|||
}
|
||||
}
|
||||
|
||||
public function from_post( $object ) {
|
||||
$this->object = $object;
|
||||
public function from_post( Post $post ) {
|
||||
$this->object = $post->to_array();
|
||||
|
||||
if ( isset( $object['published'] ) ) {
|
||||
$this->published = $object['published'];
|
||||
}
|
||||
$this->cc = array( \get_rest_url( null, '/activitypub/1.0/users/' . intval( $post->get_post_author() ) . '/followers' ) );
|
||||
|
||||
if ( isset( $object['attributedTo'] ) ) {
|
||||
$this->actor = $object['attributedTo'];
|
||||
if ( isset( $this->object['attributedTo'] ) ) {
|
||||
$this->actor = $this->object['attributedTo'];
|
||||
}
|
||||
|
||||
foreach ( $post->get_tags() as $tag ) {
|
||||
if ( 'Mention' === $tag['type'] ) {
|
||||
$this->cc[] = $tag['href'];
|
||||
}
|
||||
}
|
||||
|
||||
$type = \strtolower( $this->type );
|
||||
|
||||
if ( isset( $object['id'] ) ) {
|
||||
$this->id = add_query_arg( 'activity', $type, $object['id'] );
|
||||
if ( isset( $this->object['id'] ) ) {
|
||||
$this->id = add_query_arg( 'activity', $type, $this->object['id'] );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -278,6 +278,18 @@ class Post {
|
|||
}
|
||||
}
|
||||
|
||||
$mentions = apply_filters( 'activitypub_extract_mentions', array(), $this->post->post_content, $this );
|
||||
if ( $mentions ) {
|
||||
foreach ( $mentions as $mention => $url ) {
|
||||
$tag = array(
|
||||
'type' => 'Mention',
|
||||
'href' => $url,
|
||||
'name' => $mention,
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$this->tags = $tags;
|
||||
|
||||
return $tags;
|
||||
|
@ -444,4 +456,28 @@ class Post {
|
|||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all tags as hashtags to the post/summary content
|
||||
*
|
||||
* @param string $content
|
||||
* @param WP_Post $post
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_the_mentions() {
|
||||
$post = $this->post;
|
||||
$tags = \get_the_tags( $post->ID );
|
||||
|
||||
if ( ! $tags ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$hash_tags = array();
|
||||
|
||||
foreach ( $tags as $tag ) {
|
||||
$hash_tags[] = \sprintf( '<a rel="tag" class="u-tag u-category" href="%s">#%s</a>', \get_tag_link( $tag ), $tag->slug );
|
||||
}
|
||||
|
||||
return \implode( ' ', $hash_tags );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,409 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This is the class for integrating ActivityPub into the Friends Plugin.
|
||||
*
|
||||
* @since 0.14
|
||||
*
|
||||
* @package ActivityPub
|
||||
* @author Alex Kirk
|
||||
*/
|
||||
namespace Activitypub;
|
||||
|
||||
class Friends_Feed_Parser_ActivityPub extends \Friends\Feed_Parser {
|
||||
const SLUG = 'activitypub';
|
||||
const NAME = 'ActivityPub';
|
||||
const URL = 'https://www.w3.org/TR/activitypub/';
|
||||
private $friends_feed;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Friends\Feed $friends_feed The friends feed
|
||||
*/
|
||||
public function __construct( \Friends\Feed $friends_feed ) {
|
||||
$this->friends_feed = $friends_feed;
|
||||
|
||||
\add_action( 'activitypub_inbox', array( $this, 'handle_received_activity' ), 10, 3 );
|
||||
\add_action( 'friends_user_feed_activated', array( $this, 'queue_follow_user' ), 10 );
|
||||
\add_action( 'friends_user_feed_deactivated', array( $this, 'queue_unfollow_user' ), 10 );
|
||||
\add_action( 'friends_feed_parser_activitypub_follow', array( $this, 'follow_user' ), 10, 2 );
|
||||
\add_action( 'friends_feed_parser_activitypub_unfollow', array( $this, 'unfollow_user' ), 10, 2 );
|
||||
\add_filter( 'friends_rewrite_incoming_url', array( $this, 'friends_rewrite_incoming_url' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow logging a message via an action.
|
||||
* @param string $message The message to log.
|
||||
* @param array $objects Optional objects as meta data.
|
||||
* @return void
|
||||
*/
|
||||
private function log( $message, $objects = array() ) {
|
||||
do_action( 'friends_activitypub_log', $message, $objects );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this is a supported feed and to what degree we feel it's supported.
|
||||
*
|
||||
* @param string $url The url.
|
||||
* @param string $mime_type The mime type.
|
||||
* @param string $title The title.
|
||||
* @param string|null $content The content, it can't be assumed that it's always available.
|
||||
*
|
||||
* @return int Return 0 if unsupported, a positive value representing the confidence for the feed, use 10 if you're reasonably confident.
|
||||
*/
|
||||
public function feed_support_confidence( $url, $mime_type, $title, $content = null ) {
|
||||
if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $url ) ) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the feed title and autoselect the posts feed.
|
||||
*
|
||||
* @param array $feed_details The feed details.
|
||||
*
|
||||
* @return array The (potentially) modified feed details.
|
||||
*/
|
||||
public function update_feed_details( $feed_details ) {
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $feed_details['url'] );
|
||||
if ( ! $meta || is_wp_error( $meta ) ) {
|
||||
return $meta;
|
||||
}
|
||||
|
||||
if ( isset( $meta['name'] ) ) {
|
||||
$feed_details['title'] = $meta['name'];
|
||||
} elseif ( isset( $meta['preferredUsername'] ) ) {
|
||||
$feed_details['title'] = $meta['preferredUsername'];
|
||||
}
|
||||
|
||||
if ( isset( $meta['id'] ) ) {
|
||||
$feed_details['url'] = $meta['id'];
|
||||
}
|
||||
|
||||
return $feed_details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a Mastodon style URL @username@server to a URL via webfinger.
|
||||
*
|
||||
* @param string $url The URL to filter.
|
||||
* @param string $incoming_url Potentially a mastodon identifier.
|
||||
*
|
||||
* @return <type> ( description_of_the_return_value )
|
||||
*/
|
||||
public function friends_rewrite_incoming_url( $url, $incoming_url ) {
|
||||
if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $incoming_url ) ) {
|
||||
$resolved_url = \Activitypub\Webfinger::resolve( $incoming_url );
|
||||
if ( ! is_wp_error( $resolved_url ) ) {
|
||||
return $resolved_url;
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover the feeds available at the URL specified.
|
||||
*
|
||||
* @param string $content The content for the URL is already provided here.
|
||||
* @param string $url The url to search.
|
||||
*
|
||||
* @return array A list of supported feeds at the URL.
|
||||
*/
|
||||
public function discover_available_feeds( $content, $url ) {
|
||||
$discovered_feeds = array();
|
||||
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $url );
|
||||
if ( $meta && ! is_wp_error( $meta ) ) {
|
||||
$discovered_feeds[ $meta['id'] ] = array(
|
||||
'type' => 'application/activity+json',
|
||||
'rel' => 'self',
|
||||
'post-format' => 'status',
|
||||
'parser' => self::SLUG,
|
||||
'autoselect' => true,
|
||||
);
|
||||
}
|
||||
return $discovered_feeds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a feed and returns the processed items.
|
||||
*
|
||||
* @param string $url The url.
|
||||
*
|
||||
* @return array An array of feed items.
|
||||
*/
|
||||
public function fetch_feed( $url ) {
|
||||
// There is no feed to fetch, we'll receive items via ActivityPub.
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Create" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
* @param string $type The type of the activity.
|
||||
*/
|
||||
public function handle_received_activity( $object, $user_id, $type ) {
|
||||
if ( ! in_array(
|
||||
$type,
|
||||
array(
|
||||
// We don't need to handle 'Accept' types since it's handled by the ActivityPub plugin itself.
|
||||
'create',
|
||||
'announce',
|
||||
),
|
||||
true
|
||||
) ) {
|
||||
return false;
|
||||
}
|
||||
$actor_url = $object['actor'];
|
||||
$user_feed = false;
|
||||
if ( \wp_http_validate_url( $actor_url ) ) {
|
||||
// Let's check if we follow this actor. If not it might be a different URL representation.
|
||||
$user_feed = $this->friends_feed->get_user_feed_by_url( $actor_url );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $user_feed ) || ! \wp_http_validate_url( $actor_url ) ) {
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $actor_url );
|
||||
if ( ! $meta || ! isset( $meta['url'] ) ) {
|
||||
$this->log( 'Received invalid meta for ' . $actor_url );
|
||||
return false;
|
||||
}
|
||||
|
||||
$actor_url = $meta['url'];
|
||||
if ( ! \wp_http_validate_url( $actor_url ) ) {
|
||||
$this->log( 'Received invalid meta url for ' . $actor_url );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$user_feed = $this->friends_feed->get_user_feed_by_url( $actor_url );
|
||||
if ( ! $user_feed || is_wp_error( $user_feed ) ) {
|
||||
$this->log( 'We\'re not following ' . $actor_url );
|
||||
// We're not following this user.
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ( $type ) {
|
||||
case 'create':
|
||||
return $this->handle_incoming_post( $object['object'], $user_feed );
|
||||
case 'announce':
|
||||
return $this->handle_incoming_announce( $object['object'], $user_feed, $user_id );
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the Activity type to a post fomat.
|
||||
*
|
||||
* @param string $type The type.
|
||||
*
|
||||
* @return string The determined post format.
|
||||
*/
|
||||
private function map_type_to_post_format( $type ) {
|
||||
return 'status';
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a post for a feed, handle it.
|
||||
*
|
||||
* @param array $object The object from ActivityPub.
|
||||
* @param \Friends\User_Feed $user_feed The user feed.
|
||||
*/
|
||||
private function handle_incoming_post( $object, \Friends\User_Feed $user_feed ) {
|
||||
$permalink = $object['id'];
|
||||
if ( isset( $object['url'] ) ) {
|
||||
$permalink = $object['url'];
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'permalink' => $permalink,
|
||||
'content' => $object['content'],
|
||||
'post_format' => $this->map_type_to_post_format( $object['type'] ),
|
||||
'date' => $object['published'],
|
||||
);
|
||||
|
||||
if ( isset( $object['attributedTo'] ) ) {
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $object['attributedTo'] );
|
||||
$this->log( 'Attributed to ' . $object['attributedTo'], compact( 'meta' ) );
|
||||
if ( isset( $meta['name'] ) ) {
|
||||
$data['author'] = $meta['name'];
|
||||
} elseif ( isset( $meta['preferredUsername'] ) ) {
|
||||
$data['author'] = $meta['preferredUsername'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $object['attachment'] ) ) {
|
||||
foreach ( $object['attachment'] as $attachment ) {
|
||||
if ( ! isset( $attachment['type'] ) || ! isset( $attachment['mediaType'] ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( 'Document' !== $attachment['type'] || strpos( $attachment['mediaType'], 'image/' ) !== 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['content'] .= PHP_EOL;
|
||||
$data['content'] .= '<!-- wp:image -->';
|
||||
$data['content'] .= '<p><img src="' . esc_url( $attachment['url'] ) . '" width="' . esc_attr( $attachment['width'] ) . '" height="' . esc_attr( $attachment['height'] ) . '" class="size-full" /></p>';
|
||||
$data['content'] .= '<!-- /wp:image -->';
|
||||
}
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $object['attributedTo'] );
|
||||
$this->log( 'Attributed to ' . $object['attributedTo'], compact( 'meta' ) );
|
||||
if ( isset( $meta['name'] ) ) {
|
||||
$data['author'] = $meta['name'];
|
||||
} elseif ( isset( $meta['preferredUsername'] ) ) {
|
||||
$data['author'] = $meta['preferredUsername'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->log(
|
||||
'Received feed item',
|
||||
array(
|
||||
'url' => $permalink,
|
||||
'data' => $data,
|
||||
)
|
||||
);
|
||||
$item = new \Friends\Feed_Item( $data );
|
||||
|
||||
$this->friends_feed->process_incoming_feed_items( array( $item ), $user_feed );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* We received an announced URL (boost) for a feed, handle it.
|
||||
*
|
||||
* @param array $url The announced URL.
|
||||
* @param \Friends\User_Feed $user_feed The user feed.
|
||||
*/
|
||||
private function handle_incoming_announce( $url, \Friends\User_Feed $user_feed, $user_id ) {
|
||||
if ( ! \wp_http_validate_url( $url ) ) {
|
||||
$this->log( 'Received invalid announce', compact( 'url' ) );
|
||||
return false;
|
||||
}
|
||||
$this->log( 'Received announce for ' . $url );
|
||||
|
||||
$response = \Activitypub\safe_remote_get( $url, $user_id );
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$json = \wp_remote_retrieve_body( $response );
|
||||
$object = \json_decode( $json, true );
|
||||
if ( ! $object ) {
|
||||
$this->log( 'Received invalid json', compact( 'json' ) );
|
||||
return false;
|
||||
}
|
||||
$this->log( 'Received response', compact( 'url', 'object' ) );
|
||||
|
||||
return $this->handle_incoming_post( $object, $user_feed );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to follow the user via a scheduled event.
|
||||
*
|
||||
* @param \Friends\User_Feed $user_feed The user feed.
|
||||
*
|
||||
* @return bool|WP_Error Whether the event was queued.
|
||||
*/
|
||||
public function queue_follow_user( \Friends\User_Feed $user_feed ) {
|
||||
if ( self::SLUG !== $user_feed->get_parser() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = array( $user_feed->get_url(), get_current_user_id() );
|
||||
|
||||
$unfollow_timestamp = wp_next_scheduled( 'friends_feed_parser_activitypub_unfollow', $args );
|
||||
if ( $unfollow_timestamp ) {
|
||||
// If we just unfollowed, we don't want the event to potentially be executed after our follow event.
|
||||
wp_unschedule_event( $unfollow_timestamp, $args );
|
||||
}
|
||||
|
||||
if ( wp_next_scheduled( 'friends_feed_parser_activitypub_follow', $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return \wp_schedule_single_event( \time(), 'friends_feed_parser_activitypub_follow', $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow a user via ActivityPub at a URL.
|
||||
*
|
||||
* @param string $url The url.
|
||||
* @param int $user_id The current user id.
|
||||
*/
|
||||
public function follow_user( $url, $user_id ) {
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $url );
|
||||
$to = $meta['id'];
|
||||
$inbox = \Activitypub\get_inbox_by_actor( $to );
|
||||
$actor = \get_author_posts_url( $user_id );
|
||||
|
||||
$activity = new \Activitypub\Model\Activity( 'Follow', \Activitypub\Model\Activity::TYPE_SIMPLE );
|
||||
$activity->set_to( null );
|
||||
$activity->set_cc( null );
|
||||
$activity->set_actor( $actor );
|
||||
$activity->set_object( $to );
|
||||
$activity->set_id( $actor . '#follow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to unfollow the user via a scheduled event.
|
||||
*
|
||||
* @param \Friends\User_Feed $user_feed The user feed.
|
||||
*
|
||||
* @return bool|WP_Error Whether the event was queued.
|
||||
*/
|
||||
public function queue_unfollow_user( \Friends\User_Feed $user_feed ) {
|
||||
if ( self::SLUG !== $user_feed->get_parser() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$args = array( $user_feed->get_url(), get_current_user_id() );
|
||||
|
||||
$follow_timestamp = wp_next_scheduled( 'friends_feed_parser_activitypub_follow', $args );
|
||||
if ( $follow_timestamp ) {
|
||||
// If we just followed, we don't want the event to potentially be executed after our unfollow event.
|
||||
wp_unschedule_event( $follow_timestamp, $args );
|
||||
}
|
||||
|
||||
if ( wp_next_scheduled( 'friends_feed_parser_activitypub_unfollow', $args ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return \wp_schedule_single_event( \time(), 'friends_feed_parser_activitypub_unfollow', $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfllow a user via ActivityPub at a URL.
|
||||
*
|
||||
* @param string $url The url.
|
||||
* @param int $user_id The current user id.
|
||||
*/
|
||||
public function unfollow_user( $url, $user_id ) {
|
||||
$meta = \Activitypub\get_remote_metadata_by_actor( $url );
|
||||
$to = $meta['id'];
|
||||
$inbox = \Activitypub\get_inbox_by_actor( $to );
|
||||
$actor = \get_author_posts_url( $user_id );
|
||||
|
||||
$activity = new \Activitypub\Model\Activity( 'Undo', \Activitypub\Model\Activity::TYPE_SIMPLE );
|
||||
$activity->set_to( null );
|
||||
$activity->set_cc( null );
|
||||
$activity->set_actor( $actor );
|
||||
$activity->set_object(
|
||||
array(
|
||||
'type' => 'Follow',
|
||||
'actor' => $actor,
|
||||
'object' => $to,
|
||||
'id' => $to,
|
||||
)
|
||||
);
|
||||
$activity->set_id( $actor . '#unfollow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, $user_id );
|
||||
}
|
||||
}
|
|
@ -113,7 +113,7 @@ Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github
|
|||
|
||||
= 0.14.0 =
|
||||
|
||||
* Friends support: https://wordpress.org/plugins/friends/ . props [@akirk](https://github.com/akirk)
|
||||
* Friends support: https://wordpress.org/plugins/friends/ props [@akirk](https://github.com/akirk)
|
||||
* Massive guidance improvements. props [mediaformat](https://github.com/mediaformat) & [@akirk](https://github.com/akirk)
|
||||
* Add Custom Post Type support to outbox API. props [blueset](https://github.com/blueset)
|
||||
* Better hash-tag support. props [bocops](https://github.com/bocops)
|
||||
|
|
|
@ -19,14 +19,9 @@ require_once $_tests_dir . '/includes/functions.php';
|
|||
*/
|
||||
function _manually_load_plugin() {
|
||||
require \dirname( \dirname( __FILE__ ) ) . '/activitypub.php';
|
||||
|
||||
// Load the Friends plugin if available to test the integrations.
|
||||
$friends_plugin = \dirname( \dirname( \dirname( __FILE__ ) ) ) . '/friends/friends.php';
|
||||
if ( file_exists( $friends_plugin ) ) {
|
||||
require $friends_plugin;
|
||||
}
|
||||
}
|
||||
\tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
|
||||
|
||||
// Start up the WP testing environment.
|
||||
require $_tests_dir . '/includes/bootstrap.php';
|
||||
require __DIR__ . '/class-activitypub-testcase-cache-http.php';
|
||||
|
|
105
tests/class-activitypub-testcase-cache-http.php
Normal file
105
tests/class-activitypub-testcase-cache-http.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
class ActivityPub_TestCase_Cache_HTTP extends \WP_UnitTestCase {
|
||||
public $server;
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
// Manually activate the REST server.
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new \Spy_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
add_filter(
|
||||
'rest_url',
|
||||
function() {
|
||||
return get_option( 'home' ) . '/wp-json/';
|
||||
}
|
||||
);
|
||||
|
||||
add_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ), 10, 3 );
|
||||
add_filter( 'http_response', array( get_called_class(), 'http_response' ), 10, 3 );
|
||||
}
|
||||
|
||||
public function tear_down() {
|
||||
remove_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ) );
|
||||
remove_filter( 'http_response', array( get_called_class(), 'http_response' ) );
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
|
||||
public static function pre_http_request( $preempt, $request, $url ) {
|
||||
$p = wp_parse_url( $url );
|
||||
$cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json';
|
||||
if ( file_exists( $cache ) ) {
|
||||
return apply_filters(
|
||||
'fake_http_response',
|
||||
json_decode( file_get_contents( $cache ), true ), // phpcs:ignore
|
||||
$p['scheme'] . '://' . $p['host'],
|
||||
$url,
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
$home_url = home_url();
|
||||
|
||||
// Pretend the url now is the requested one.
|
||||
update_option( 'home', $p['scheme'] . '://' . $p['host'] );
|
||||
$rest_prefix = home_url() . '/wp-json';
|
||||
|
||||
if ( false === strpos( $url, $rest_prefix ) ) {
|
||||
// Restore the old home_url.
|
||||
update_option( 'home', $home_url );
|
||||
return $preempt;
|
||||
}
|
||||
|
||||
$url = substr( $url, strlen( $rest_prefix ) );
|
||||
$r = new \WP_REST_Request( $request['method'], $url );
|
||||
if ( ! empty( $request['body'] ) ) {
|
||||
foreach ( $request['body'] as $key => $value ) {
|
||||
$r->set_param( $key, $value );
|
||||
}
|
||||
}
|
||||
global $wp_rest_server;
|
||||
$response = $wp_rest_server->dispatch( $r );
|
||||
// Restore the old url.
|
||||
update_option( 'home', $home_url );
|
||||
|
||||
return apply_filters(
|
||||
'fake_http_response',
|
||||
array(
|
||||
'headers' => array(
|
||||
'content-type' => 'text/json',
|
||||
),
|
||||
'body' => wp_json_encode( $response->data ),
|
||||
'response' => array(
|
||||
'code' => $response->status,
|
||||
),
|
||||
),
|
||||
$p['scheme'] . '://' . $p['host'],
|
||||
$url,
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
public static function http_response( $response, $args, $url ) {
|
||||
$p = wp_parse_url( $url );
|
||||
$cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json';
|
||||
if ( ! file_exists( $cache ) ) {
|
||||
$headers = wp_remote_retrieve_headers( $response );
|
||||
file_put_contents( // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
|
||||
$cache,
|
||||
wp_json_encode(
|
||||
array(
|
||||
'headers' => $headers->getAll(),
|
||||
'body' => wp_remote_retrieve_body( $response ),
|
||||
'response' => array(
|
||||
'code' => wp_remote_retrieve_response_code( $response ),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
22
tests/fixtures/notiz-blog-author-matthias-pfefferle.json
vendored
Normal file
22
tests/fixtures/notiz-blog-author-matthias-pfefferle.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"headers": {
|
||||
"date": "Fri, 09 Dec 2022 10:39:51 GMT",
|
||||
"content-type": "application\/activity+json",
|
||||
"server": "nginx",
|
||||
"x-xrds-location": "https:\/\/notiz.blog\/?xrds",
|
||||
"x-yadis-location": "https:\/\/notiz.blog\/?xrds",
|
||||
"link": "<https:\/\/notiz.blog\/wp-api\/micropub\/1.0\/media>; rel=\"micropub_media\", <https:\/\/notiz.blog\/wp-api\/micropub\/1.0\/endpoint>; rel=\"micropub\", <https:\/\/notiz.blog\/wp-api\/friends\/v1>; rel=\"friends-base-url\", <https:\/\/notiz.blog\/wp-api\/indieauth\/1.0\/auth>; rel=\"authorization_endpoint\", <https:\/\/notiz.blog\/wp-api\/indieauth\/1.0\/token>; rel=\"token_endpoint\", <https:\/\/notiz.blog\/wp-api\/indieauth\/1.0\/metadata>; rel=\"indieauth-metadata\", <https:\/\/notiz.blog\/wp-api\/>; rel=\"https:\/\/api.w.org\/\", <https:\/\/notiz.blog\/wp-api\/wp\/v2\/users\/1>; rel=\"alternate\"; type=\"application\/json\"",
|
||||
"cache-control": "max-age=0, public",
|
||||
"expires": "Fri, 09 Dec 2022 10:39:51 GMT",
|
||||
"x-xss-protection": "1; mode=block",
|
||||
"x-content-type-options": "nosniff",
|
||||
"strict-transport-security": "max-age=31536000",
|
||||
"x-frame-options": "SAMEORIGIN",
|
||||
"referrer-policy": "strict-origin-when-cross-origin",
|
||||
"x-clacks-overhead": "GNU Terry Pratchett"
|
||||
},
|
||||
"body": "{\"@context\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams\",\"https:\\\/\\\/w3id.org\\\/security\\\/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"PropertyValue\":\"schema:PropertyValue\",\"schema\":\"http:\\\/\\\/schema.org#\",\"pt\":\"https:\\\/\\\/joinpeertube.org\\\/ns#\",\"toot\":\"http:\\\/\\\/joinmastodon.org\\\/ns#\",\"value\":\"schema:value\",\"Hashtag\":\"as:Hashtag\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"featuredTags\":{\"@id\":\"toot:featuredTags\",\"@type\":\"@id\"}}],\"id\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"type\":\"Person\",\"name\":\"Matthias Pfefferle\",\"summary\":\"Ich bin Webworker und arbeite als \\u0022Head of WordPress Development\\u0022 f\\u00fcr IONOS in Karlsruhe. Ich blogge, podcaste und schreibe \\u003Cdel\\u003Eeine Kolumne\\u003C\\\/del\\u003E \\u00fcber das open, independent und federated social Web. \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/about\\\/\\u0022\\u003EMehr \\u00fcber mich.\\u003C\\\/a\\u003E\",\"preferredUsername\":\"pfefferle\",\"url\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"icon\":{\"type\":\"Image\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/75512bb584bbceae57dfc503692b16b2?s=120\\u0026d=mm\\u0026r=g\"},\"image\":{\"type\":\"Image\",\"url\":\"https:\\\/\\\/notiz.blog\\\/wp-content\\\/uploads\\\/2017\\\/02\\\/cropped-Unknown-2.jpeg\"},\"inbox\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/inbox\",\"outbox\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/outbox\",\"followers\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/followers\",\"following\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/following\",\"manuallyApprovesFollowers\":false,\"publicKey\":{\"id\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/#main-key\",\"owner\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA039CnlArzn6nsRjcC2RJ\\nrjY3K5ZrLnFUbPtHLGNXMJUGW+rFYE1DzhdKPTj9giiXE+J7ADI0Tme5rSWw14bT\\nLhOMBs2ma8d03\\\/wnF1+kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOL\\nncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG\\\/mjBOUwl5FjgcGt9aQARd\\nmFxW5XydxfNrCZwuE34Zbq\\\/IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q\\n5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENK\\nCwIDAQAB\\n-----END PUBLIC KEY-----\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"Blog\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/notiz.blog\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/\\u0022\\u003Enotiz.blog\\u003C\\\/a\\u003E\"},{\"type\":\"PropertyValue\",\"name\":\"Profil\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\\u0022\\u003Enotiz.blog\\u003C\\\/a\\u003E\"},{\"type\":\"PropertyValue\",\"name\":\"Website\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/pfefferle.org\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/pfefferle.org\\\/\\u0022\\u003Epfefferle.org\\u003C\\\/a\\u003E\"}]}",
|
||||
"response": {
|
||||
"code": 200
|
||||
}
|
||||
}
|
22
tests/fixtures/notiz-blog-well-known-webfinger.json
vendored
Normal file
22
tests/fixtures/notiz-blog-well-known-webfinger.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"headers": {
|
||||
"date": "Fri, 09 Dec 2022 10:39:51 GMT",
|
||||
"content-type": "application\/jrd+json; charset=UTF-8",
|
||||
"server": "nginx",
|
||||
"x-xrds-location": "https:\/\/notiz.blog\/?xrds",
|
||||
"x-yadis-location": "https:\/\/notiz.blog\/?xrds",
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "max-age=2592000, public",
|
||||
"expires": "Sun, 08 Jan 2023 10:39:50 GMT",
|
||||
"x-xss-protection": "1; mode=block",
|
||||
"x-content-type-options": "nosniff",
|
||||
"strict-transport-security": "max-age=31536000",
|
||||
"x-frame-options": "SAMEORIGIN",
|
||||
"referrer-policy": "strict-origin-when-cross-origin",
|
||||
"x-clacks-overhead": "GNU Terry Pratchett"
|
||||
},
|
||||
"body": "{\"subject\":\"acct:pfefferle@notiz.blog\",\"aliases\":[\"acct:pfefferle@notiz.blog\",\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"mailto:pfefferle@notiz.blog\"],\"links\":[{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/profile-page\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"type\":\"text\\\/html\"},{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/avatar\",\"href\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/75512bb584bbceae57dfc503692b16b2?s=96&d=mm&r=g\"},{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/profile-page\",\"href\":\"https:\\\/\\\/pfefferle.org\\\/\",\"type\":\"text\\\/html\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/www.paypal.me\\\/matthiaspfefferle\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/liberapay.com\\\/pfefferle\\\/\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/notiz.blog\\\/donate\\\/\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/flattr.com\\\/@pfefferle\"},{\"href\":\"https:\\\/\\\/notiz.blog\\\/\",\"rel\":\"http:\\\/\\\/specs.openid.net\\\/auth\\\/2.0\\\/provider\"},{\"rel\":\"self\",\"type\":\"application\\\/activity+json\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\"},{\"rel\":\"micropub_media\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/micropub\\\/1.0\\\/media\"},{\"rel\":\"micropub\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/micropub\\\/1.0\\\/endpoint\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/2.0\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/2.0\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/1.1\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/1.1\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/1.0\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/1.0\"},{\"rel\":\"https:\\\/\\\/feneas.org\\\/ns\\\/serviceinfo\",\"type\":\"application\\\/ld+json\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/serviceinfo\\\/1.0\",\"properties\":{\"https:\\\/\\\/feneas.org\\\/ns\\\/serviceinfo#software.name\":\"notizBlog\"}},{\"rel\":\"http:\\\/\\\/schemas.google.com\\\/g\\\/2010#updates-from\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/ostatus\\\/\",\"type\":\"application\\\/atom+xml\"},{\"rel\":\"http:\\\/\\\/ostatus.org\\\/schema\\\/1.0\\\/subscribe\",\"template\":\"https:\\\/\\\/notiz.blog\\\/?profile={uri}\"},{\"rel\":\"magic-public-key\",\"href\":\"data:application\\\/magic-public-key,RSA.039CnlArzn6nsRjcC2RJrjY3K5ZrLnFUbPtHLGNXMJUGW-rFYE1DzhdKPTj9giiXE-J7ADI0Tme5rSWw14bTLhOMBs2ma8d03_wnF1-kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOLncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG_mjBOUwl5FjgcGt9aQARdmFxW5XydxfNrCZwuE34Zbq_IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENKCw==.AQAB\"},{\"rel\":\"salmon\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/salmon-replies\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/salmon-mention\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"feed\",\"type\":\"application\\\/stream+json\",\"title\":\"Activity-Streams 1.0 Feed\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/as1\\\/\"},{\"rel\":\"feed\",\"type\":\"application\\\/activity+json\",\"title\":\"Activity-Streams 2.0 Feed\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/as2\\\/\"},{\"rel\":\"http:\\\/\\\/oexchange.org\\\/spec\\\/0.8\\\/rel\\\/user-target\",\"href\":\"https:\\\/\\\/notiz.blog\\\/?oexchange=xrd\",\"type\":\"application\\\/xrd+xml\"},{\"rel\":\"http:\\\/\\\/a9.com\\\/-\\\/spec\\\/opensearch\\\/1.1\\\/\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/opensearch\\\/1.1\\\/document\",\"type\":\"application\\\/opensearchdescription+xml\"},{\"rel\":\"describedby\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/foaf\\\/\",\"type\":\"application\\\/rdf+xml\"},{\"rel\":\"webmention\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/webmention\\\/1.0\\\/endpoint\"},{\"rel\":\"http:\\\/\\\/webmention.org\\\/\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/webmention\\\/1.0\\\/endpoint\"}],\"properties\":{\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/magic-key\":\"RSA.039CnlArzn6nsRjcC2RJrjY3K5ZrLnFUbPtHLGNXMJUGW-rFYE1DzhdKPTj9giiXE-J7ADI0Tme5rSWw14bTLhOMBs2ma8d03_wnF1-kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOLncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG_mjBOUwl5FjgcGt9aQARdmFxW5XydxfNrCZwuE34Zbq_IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENKCw==.AQAB\"}}",
|
||||
"response": {
|
||||
"code": 200
|
||||
}
|
||||
}
|
135
tests/test-class-activitypub-activity-dispatcher.php
Normal file
135
tests/test-class-activitypub-activity-dispatcher.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
class Test_Activitypub_Activity_Dispatcher extends ActivityPub_TestCase_Cache_HTTP {
|
||||
public static $users = array(
|
||||
'username@example.org' => array(
|
||||
'url' => 'https://example.org/users/username',
|
||||
'inbox' => 'https://example.org/users/username/inbox',
|
||||
'name' => 'username',
|
||||
),
|
||||
'jon@example.com' => array(
|
||||
'url' => 'https://example.com/author/jon',
|
||||
'inbox' => 'https://example.com/author/jon/inbox',
|
||||
'name' => 'jon',
|
||||
),
|
||||
);
|
||||
|
||||
public function test_dispatch_activity() {
|
||||
$followers = array( 'https://example.com/author/jon', 'https://example.org/users/username' );
|
||||
\update_user_meta( 1, 'activitypub_followers', $followers );
|
||||
|
||||
$post = \wp_insert_post(
|
||||
array(
|
||||
'post_author' => 1,
|
||||
'post_content' => 'hello',
|
||||
)
|
||||
);
|
||||
|
||||
$pre_http_request = new MockAction();
|
||||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
\Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post );
|
||||
|
||||
$this->assertSame( 2, $pre_http_request->get_call_count() );
|
||||
$all_args = $pre_http_request->get_args();
|
||||
$first_call_args = array_shift( $all_args );
|
||||
$this->assertEquals( 'https://example.com/author/jon/inbox', $first_call_args[2] );
|
||||
|
||||
$second_call_args = array_shift( $all_args );
|
||||
$this->assertEquals( 'https://example.org/users/username/inbox', $second_call_args[2] );
|
||||
|
||||
remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 );
|
||||
}
|
||||
public function test_dispatch_mentions() {
|
||||
$post = \wp_insert_post(
|
||||
array(
|
||||
'post_author' => 1,
|
||||
'post_content' => '@alex hello',
|
||||
)
|
||||
);
|
||||
|
||||
self::$users['https://example.com/alex'] = array(
|
||||
'url' => 'https://example.com/alex',
|
||||
'inbox' => 'https://example.com/alex/inbox',
|
||||
'name' => 'alex',
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'activitypub_extract_mentions',
|
||||
function( $mentions ) {
|
||||
$mentions[] = 'https://example.com/alex';
|
||||
return $mentions;
|
||||
},
|
||||
10
|
||||
);
|
||||
|
||||
$pre_http_request = new MockAction();
|
||||
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
\Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post );
|
||||
|
||||
$this->assertSame( 1, $pre_http_request->get_call_count() );
|
||||
$all_args = $pre_http_request->get_args();
|
||||
$first_call_args = $all_args[0];
|
||||
$this->assertEquals( 'https://example.com/alex/inbox', $first_call_args[2] );
|
||||
|
||||
$body = json_decode( $first_call_args[1]['body'], true );
|
||||
$this->assertArrayHasKey( 'id', $body );
|
||||
|
||||
remove_all_filters( 'activitypub_from_post_object' );
|
||||
remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 );
|
||||
}
|
||||
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
|
||||
_delete_all_posts();
|
||||
}
|
||||
|
||||
public function tear_down() {
|
||||
remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
public static function pre_get_remote_metadata_by_actor( $pre, $actor ) {
|
||||
if ( isset( self::$users[ $actor ] ) ) {
|
||||
return self::$users[ $actor ];
|
||||
}
|
||||
foreach ( self::$users as $username => $data ) {
|
||||
if ( $data['url'] === $actor ) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
return $pre;
|
||||
}
|
||||
|
||||
public static function http_request_host_is_external( $in, $host ) {
|
||||
if ( in_array( $host, array( 'example.com', 'example.org' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
return $in;
|
||||
}
|
||||
public static function http_request_args( $args, $url ) {
|
||||
if ( in_array( wp_parse_url( $url, PHP_URL_HOST ), array( 'example.com', 'example.org' ), true ) ) {
|
||||
$args['reject_unsafe_urls'] = false;
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
public static function pre_http_request( $preempt, $request, $url ) {
|
||||
return array(
|
||||
'headers' => array(
|
||||
'content-type' => 'text/json',
|
||||
),
|
||||
'body' => '',
|
||||
'response' => array(
|
||||
'code' => 202,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public static function http_response( $response, $args, $url ) {
|
||||
return $response;
|
||||
}
|
||||
}
|
31
tests/test-class-activitypub-activity.php
Normal file
31
tests/test-class-activitypub-activity.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
class Test_Activitypub_Activity extends WP_UnitTestCase {
|
||||
public function test_activity_mentions() {
|
||||
$post = \wp_insert_post(
|
||||
array(
|
||||
'post_author' => 1,
|
||||
'post_content' => '@alex hello',
|
||||
)
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'activitypub_extract_mentions',
|
||||
function( $mentions ) {
|
||||
$mentions['@alex'] = 'https://example.com/alex';
|
||||
return $mentions;
|
||||
},
|
||||
10
|
||||
);
|
||||
|
||||
$activitypub_post = new \Activitypub\Model\Post( $post );
|
||||
|
||||
$activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL );
|
||||
$activitypub_activity->from_post( $activitypub_post );
|
||||
|
||||
$this->assertContains( \get_rest_url( null, '/activitypub/1.0/users/1/followers' ), $activitypub_activity->get_cc() );
|
||||
$this->assertContains( 'https://example.com/alex', $activitypub_activity->get_cc() );
|
||||
|
||||
remove_all_filters( 'activitypub_extract_mentions' );
|
||||
\wp_trash_post( $post );
|
||||
}
|
||||
}
|
|
@ -4,26 +4,26 @@ class Test_Activitypub_Hashtag extends WP_UnitTestCase {
|
|||
* @dataProvider the_content_provider
|
||||
*/
|
||||
public function test_the_content( $content, $content_with_hashtag ) {
|
||||
$content = \Activitypub\Hashtag::the_content( $content );
|
||||
|
||||
$this->assertEquals( $content_with_hashtag, $content );
|
||||
}
|
||||
|
||||
public function the_content_provider() {
|
||||
\wp_create_term( 'object', 'post_tag' );
|
||||
$object = \get_term_by( 'name', 'object', 'post_tag' );
|
||||
$link = \get_term_link( $object, 'post_tag' );
|
||||
|
||||
$content = \Activitypub\Hashtag::the_content( $content );
|
||||
|
||||
$this->assertEquals( sprintf( $content_with_hashtag, $link ), $content );
|
||||
}
|
||||
|
||||
public function the_content_provider() {
|
||||
return array(
|
||||
array( 'test', 'test' ),
|
||||
array( '#test', '#test' ),
|
||||
array( 'hallo #test test', 'hallo #test test' ),
|
||||
array( 'hallo #object test', 'hallo <a rel="tag" class="u-tag u-category" href="' . $link . '">#object</a> test' ),
|
||||
array( '#object test', '<a rel="tag" class="u-tag u-category" href="' . $link . '">#object</a> test' ),
|
||||
array( 'hallo #object test', 'hallo <a rel="tag" class="u-tag u-category" href="%s">#object</a> test' ),
|
||||
array( '#object test', '<a rel="tag" class="u-tag u-category" href="%s">#object</a> test' ),
|
||||
array( 'hallo <a href="http://test.test/#object">test</a> test', 'hallo <a href="http://test.test/#object">test</a> test' ),
|
||||
array( 'hallo <a href="http://test.test/#object">#test</a> test', 'hallo <a href="http://test.test/#object">#test</a> test' ),
|
||||
array( '<div>hallo #object test</div>', '<div>hallo <a rel="tag" class="u-tag u-category" href="' . $link . '">#object</a> test</div>' ),
|
||||
array( '<div>hallo #object</div>', '<div>hallo <a rel="tag" class="u-tag u-category" href="' . $link . '">#object</a></div>' ),
|
||||
array( '<div>hallo #object test</div>', '<div>hallo <a rel="tag" class="u-tag u-category" href="%s">#object</a> test</div>' ),
|
||||
array( '<div>hallo #object</div>', '<div>hallo <a rel="tag" class="u-tag u-category" href="%s">#object</a></div>' ),
|
||||
array( '<div>#object</div>', '<div>#object</div>' ),
|
||||
array( '<a>#object</a>', '<a>#object</a>' ),
|
||||
array( '<div style="color: #ccc;">object</a>', '<div style="color: #ccc;">object</a>' ),
|
||||
|
|
37
tests/test-class-activitypub-mention.php
Normal file
37
tests/test-class-activitypub-mention.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
class Test_Activitypub_Mention extends ActivityPub_TestCase_Cache_HTTP {
|
||||
public static $users = array(
|
||||
'username@example.org' => array(
|
||||
'url' => 'https://example.org/users/username',
|
||||
'name' => 'username',
|
||||
),
|
||||
);
|
||||
/**
|
||||
* @dataProvider the_content_provider
|
||||
*/
|
||||
public function test_the_content( $content, $content_with_mention ) {
|
||||
add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
|
||||
$content = \Activitypub\Mention::the_content( $content );
|
||||
remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );
|
||||
|
||||
$this->assertEquals( $content_with_mention, $content );
|
||||
}
|
||||
|
||||
public function the_content_provider() {
|
||||
return array(
|
||||
array( 'hallo @username@example.org test', 'hallo <a rel="mention" class="u-url mention" href="https://example.org/users/username">@<span>username</span></a> test' ),
|
||||
array( 'hallo @pfefferle@notiz.blog test', 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@<span>pfefferle</span></a> test' ),
|
||||
array( 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@<span>pfefferle</span>@notiz.blog</a> test', 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@<span>pfefferle</span>@notiz.blog</a> test' ),
|
||||
array( 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@pfefferle@notiz.blog</a> test', 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/author/matthias-pfefferle/">@pfefferle@notiz.blog</a> test' ),
|
||||
array( 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/@pfefferle/">@pfefferle@notiz.blog</a> test', 'hallo <a rel="mention" class="u-url mention" href="https://notiz.blog/@pfefferle/">@pfefferle@notiz.blog</a> test' ),
|
||||
);
|
||||
}
|
||||
|
||||
public static function pre_get_remote_metadata_by_actor( $pre, $actor ) {
|
||||
$actor = ltrim( $actor, '@' );
|
||||
if ( isset( self::$users[ $actor ] ) ) {
|
||||
return self::$users[ $actor ];
|
||||
}
|
||||
return $pre;
|
||||
}
|
||||
}
|
|
@ -1,326 +0,0 @@
|
|||
<?php
|
||||
|
||||
class Test_Friends_Feed_Parser_ActivityPub extends \WP_UnitTestCase {
|
||||
public static $users = array();
|
||||
private $friend_id;
|
||||
private $friend_name;
|
||||
private $actor;
|
||||
|
||||
public function test_incoming_post() {
|
||||
$now = time() - 10;
|
||||
$status_id = 123;
|
||||
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'post_type' => \Friends\Friends::CPT,
|
||||
'author' => $this->friend_id,
|
||||
)
|
||||
);
|
||||
|
||||
$post_count = count( $posts );
|
||||
|
||||
// Let's post a new Note through the REST API.
|
||||
$date = gmdate( \DATE_W3C, $now++ );
|
||||
$id = 'test' . $status_id;
|
||||
$content = 'Test ' . $date . ' ' . rand();
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/' . get_current_user_id() . '/inbox' );
|
||||
$request->set_param( 'type', 'Create' );
|
||||
$request->set_param( 'id', $id );
|
||||
$request->set_param( 'actor', $this->actor );
|
||||
|
||||
$attachment_url = 'https://mastodon.local/files/original/1234.png';
|
||||
$attachment_width = 400;
|
||||
$attachment_height = 600;
|
||||
$request->set_param(
|
||||
'object',
|
||||
array(
|
||||
'type' => 'Note',
|
||||
'id' => $id,
|
||||
'attributedTo' => $this->actor,
|
||||
'content' => $content,
|
||||
'url' => 'https://mastodon.local/users/akirk/statuses/' . ( $status_id++ ),
|
||||
'published' => $date,
|
||||
'attachment' => array(
|
||||
array(
|
||||
'type' => 'Document',
|
||||
'mediaType' => 'image/png',
|
||||
'url' => $attachment_url,
|
||||
'name' => '',
|
||||
'blurhash' => '',
|
||||
'width' => $attachment_width,
|
||||
'height' => $attachment_height,
|
||||
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'post_type' => \Friends\Friends::CPT,
|
||||
'author' => $this->friend_id,
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals( $post_count + 1, count( $posts ) );
|
||||
$this->assertStringStartsWith( $content, $posts[0]->post_content );
|
||||
$this->assertStringContainsString( '<img src="' . esc_url( $attachment_url ) . '" width="' . esc_attr( $attachment_width ) . '" height="' . esc_attr( $attachment_height ) . '"', $posts[0]->post_content );
|
||||
$this->assertEquals( $this->friend_id, $posts[0]->post_author );
|
||||
|
||||
// Do another test post, this time with a URL that has an @-id.
|
||||
$date = gmdate( \DATE_W3C, $now++ );
|
||||
$id = 'test' . $status_id;
|
||||
$content = 'Test ' . $date . ' ' . rand();
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/' . get_current_user_id() . '/inbox' );
|
||||
$request->set_param( 'type', 'Create' );
|
||||
$request->set_param( 'id', $id );
|
||||
$request->set_param( 'actor', 'https://mastodon.local/@akirk' );
|
||||
$request->set_param(
|
||||
'object',
|
||||
array(
|
||||
'type' => 'Note',
|
||||
'id' => $id,
|
||||
'attributedTo' => 'https://mastodon.local/@akirk',
|
||||
'content' => $content,
|
||||
'url' => 'https://mastodon.local/users/akirk/statuses/' . ( $status_id++ ),
|
||||
'published' => $date,
|
||||
)
|
||||
);
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'post_type' => \Friends\Friends::CPT,
|
||||
'author' => $this->friend_id,
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals( $post_count + 2, count( $posts ) );
|
||||
$this->assertEquals( $content, $posts[0]->post_content );
|
||||
$this->assertEquals( $this->friend_id, $posts[0]->post_author );
|
||||
$this->assertEquals( $this->friend_name, get_post_meta( $posts[0]->ID, 'author', true ) );
|
||||
}
|
||||
|
||||
public function test_incoming_announce() {
|
||||
$now = time() - 10;
|
||||
$status_id = 123;
|
||||
|
||||
self::$users['https://notiz.blog/author/matthias-pfefferle/'] = array(
|
||||
'url' => 'https://notiz.blog/author/matthias-pfefferle/',
|
||||
'name' => 'Matthias Pfefferle',
|
||||
);
|
||||
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'post_type' => \Friends\Friends::CPT,
|
||||
'author' => $this->friend_id,
|
||||
)
|
||||
);
|
||||
|
||||
$post_count = count( $posts );
|
||||
|
||||
$date = gmdate( \DATE_W3C, $now++ );
|
||||
$id = 'test' . $status_id;
|
||||
|
||||
$object = 'https://notiz.blog/2022/11/14/the-at-protocol/';
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/activitypub/1.0/users/' . get_current_user_id() . '/inbox' );
|
||||
$request->set_param( 'type', 'Announce' );
|
||||
$request->set_param( 'id', $id );
|
||||
$request->set_param( 'actor', $this->actor );
|
||||
$request->set_param( 'published', $date );
|
||||
$request->set_param( 'object', $object );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 202, $response->get_status() );
|
||||
|
||||
$p = wp_parse_url( $object );
|
||||
$cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.response';
|
||||
$this->assertFileExists( $cache );
|
||||
|
||||
$object = json_decode( wp_remote_retrieve_body( unserialize( file_get_contents( $cache ) ) ) );
|
||||
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'post_type' => \Friends\Friends::CPT,
|
||||
'author' => $this->friend_id,
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals( $post_count + 1, count( $posts ) );
|
||||
$this->assertStringContainsString( 'Dezentrale Netzwerke', $posts[0]->post_content );
|
||||
$this->assertEquals( $this->friend_id, $posts[0]->post_author );
|
||||
$this->assertEquals( 'Matthias Pfefferle', get_post_meta( $posts[0]->ID, 'author', true ) );
|
||||
|
||||
}
|
||||
public function set_up() {
|
||||
if ( ! class_exists( '\Friends\Friends' ) ) {
|
||||
return $this->markTestSkipped( 'The Friends plugin is not loaded.' );
|
||||
}
|
||||
parent::set_up();
|
||||
|
||||
// Manually activate the REST server.
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = new \Spy_REST_Server();
|
||||
$this->server = $wp_rest_server;
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
add_filter(
|
||||
'rest_url',
|
||||
function() {
|
||||
return get_option( 'home' ) . '/wp-json/';
|
||||
}
|
||||
);
|
||||
|
||||
add_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ), 10, 3 );
|
||||
add_filter( 'http_response', array( get_called_class(), 'http_response' ), 10, 3 );
|
||||
add_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ), 10, 2 );
|
||||
add_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ), 10, 2 );
|
||||
add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
|
||||
|
||||
$user_id = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
wp_set_current_user( $user_id );
|
||||
|
||||
$this->friend_name = 'Alex Kirk';
|
||||
$this->actor = 'https://mastodon.local/users/akirk';
|
||||
|
||||
$user_feed = \Friends\User_Feed::get_by_url( $this->actor );
|
||||
if ( is_wp_error( $user_feed ) ) {
|
||||
$this->friend_id = $this->factory->user->create(
|
||||
array(
|
||||
'display_name' => $this->friend_name,
|
||||
'role' => 'friend',
|
||||
)
|
||||
);
|
||||
\Friends\User_Feed::save(
|
||||
new \Friends\User( $this->friend_id ),
|
||||
$this->actor,
|
||||
array(
|
||||
'parser' => 'activitypub',
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$this->friend_id = $user_feed->get_friend_user()->ID;
|
||||
}
|
||||
|
||||
self::$users[ $this->actor ] = array(
|
||||
'url' => $this->actor,
|
||||
'name' => $this->friend_name,
|
||||
);
|
||||
self::$users['https://mastodon.local/@akirk'] = self::$users[ $this->actor ];
|
||||
|
||||
_delete_all_posts();
|
||||
}
|
||||
|
||||
public function tear_down() {
|
||||
remove_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ) );
|
||||
remove_filter( 'http_response', array( get_called_class(), 'http_response' ) );
|
||||
remove_filter( 'http_request_host_is_external', array( get_called_class(), 'http_request_host_is_external' ) );
|
||||
remove_filter( 'http_request_args', array( get_called_class(), 'http_request_args' ) );
|
||||
remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );
|
||||
}
|
||||
|
||||
public static function pre_get_remote_metadata_by_actor( $pre, $actor ) {
|
||||
if ( isset( self::$users[ $actor ] ) ) {
|
||||
return self::$users[ $actor ];
|
||||
}
|
||||
return $pre;
|
||||
}
|
||||
public static function http_request_host_is_external( $in, $host ) {
|
||||
if ( in_array( $host, array( 'mastodon.local' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
return $in;
|
||||
}
|
||||
public static function http_request_args( $args, $url ) {
|
||||
if ( in_array( parse_url( $url, PHP_URL_HOST ), array( 'mastodon.local' ), true ) ) {
|
||||
$args['reject_unsafe_urls'] = false;
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
public static function pre_http_request( $preempt, $request, $url ) {
|
||||
$p = wp_parse_url( $url );
|
||||
$cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.response';
|
||||
if ( file_exists( $cache ) ) {
|
||||
return apply_filters(
|
||||
'fake_http_response',
|
||||
unserialize( file_get_contents( $cache ) ),
|
||||
$p['scheme'] . '://' . $p['host'],
|
||||
$url,
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
$home_url = home_url();
|
||||
|
||||
// Pretend the url now is the requested one.
|
||||
update_option( 'home', $p['scheme'] . '://' . $p['host'] );
|
||||
$rest_prefix = home_url() . '/wp-json';
|
||||
|
||||
if ( false === strpos( $url, $rest_prefix ) ) {
|
||||
// Restore the old home_url.
|
||||
update_option( 'home', $home_url );
|
||||
return $preempt;
|
||||
}
|
||||
|
||||
$url = substr( $url, strlen( $rest_prefix ) );
|
||||
$r = new \WP_REST_Request( $request['method'], $url );
|
||||
if ( ! empty( $request['body'] ) ) {
|
||||
foreach ( $request['body'] as $key => $value ) {
|
||||
$r->set_param( $key, $value );
|
||||
}
|
||||
}
|
||||
global $wp_rest_server;
|
||||
$response = $wp_rest_server->dispatch( $r );
|
||||
// Restore the old url.
|
||||
update_option( 'home', $home_url );
|
||||
|
||||
return apply_filters(
|
||||
'fake_http_response',
|
||||
array(
|
||||
'headers' => array(
|
||||
'content-type' => 'text/json',
|
||||
),
|
||||
'body' => wp_json_encode( $response->data ),
|
||||
'response' => array(
|
||||
'code' => $response->status,
|
||||
),
|
||||
),
|
||||
$p['scheme'] . '://' . $p['host'],
|
||||
$url,
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
public static function http_response( $response, $args, $url ) {
|
||||
$p = wp_parse_url( $url );
|
||||
$cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.response';
|
||||
if ( ! file_exists( $cache ) ) {
|
||||
$headers = wp_remote_retrieve_headers( $response );
|
||||
file_put_contents(
|
||||
$cache,
|
||||
serialize(
|
||||
array(
|
||||
'headers' => $headers->getAll(),
|
||||
'body' => wp_remote_retrieve_body( $response ),
|
||||
'response' => array(
|
||||
'code' => wp_remote_retrieve_response_code( $response ),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
9
tests/test-functions.php
Normal file
9
tests/test-functions.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
class Test_Functions extends ActivityPub_TestCase_Cache_HTTP {
|
||||
public function test_get_remote_metadata_by_actor() {
|
||||
$metadata = \ActivityPub\get_remote_metadata_by_actor( 'pfefferle@notiz.blog' );
|
||||
$this->assertEquals( 'https://notiz.blog/author/matthias-pfefferle/', $metadata['url'] );
|
||||
$this->assertEquals( 'pfefferle', $metadata['preferredUsername'] );
|
||||
$this->assertEquals( 'Matthias Pfefferle', $metadata['name'] );
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue