updated plugin `ActivityPub` version 0.12.0

This commit is contained in:
KawaiiPunk 2020-12-25 19:23:08 +00:00 committed by Gitium
parent 440a778b7c
commit 8ded57667b
25 changed files with 597 additions and 600 deletions

View File

@ -3,7 +3,7 @@
* Plugin Name: ActivityPub
* Plugin URI: https://github.com/pfefferle/wordpress-activitypub/
* Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
* Version: 0.10.1
* Version: 0.12.0
* Author: Matthias Pfefferle
* Author URI: https://notiz.blog/
* License: MIT
@ -20,18 +20,19 @@ namespace Activitypub;
*/
function init() {
\defined( 'ACTIVITYPUB_HASHTAGS_REGEXP' ) || \define( 'ACTIVITYPUB_HASHTAGS_REGEXP', '(?:(?<=\s)|^)#(\w*[A-Za-z_]+\w*)' );
\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>%title%</strong></p>\n\n%content%\n\n<p>%hashtags%</p>\n\n<p>%shortlink%</p>" );
require_once \dirname( __FILE__ ) . '/includes/table/followers-list.php';
require_once \dirname( __FILE__ ) . '/includes/class-signature.php';
require_once \dirname( __FILE__ ) . '/includes/peer/class-followers.php';
require_once \dirname( __FILE__ ) . '/includes/functions.php';
require_once \dirname( __FILE__ ) . '/includes/class-activity-dispatcher.php';
\Activitypub\Activity_Dispatcher::init();
require_once \dirname( __FILE__ ) . '/includes/model/class-activity.php';
require_once \dirname( __FILE__ ) . '/includes/model/class-post.php';
\Activitypub\Model\Post::init();
require_once \dirname( __FILE__ ) . '/includes/class-activity-dispatcher.php';
\Activitypub\Activity_Dispatcher::init();
require_once \dirname( __FILE__ ) . '/includes/class-activitypub.php';
\Activitypub\Activitypub::init();
@ -52,8 +53,11 @@ function init() {
require_once \dirname( __FILE__ ) . '/includes/rest/class-webfinger.php';
\Activitypub\Rest\Webfinger::init();
require_once \dirname( __FILE__ ) . '/includes/rest/class-nodeinfo.php';
\Activitypub\Rest\NodeInfo::init();
// load NodeInfo endpoints only if blog is public
if ( 1 === \get_option( 'blog_public', 1 ) ) {
require_once \dirname( __FILE__ ) . '/includes/rest/class-nodeinfo.php';
\Activitypub\Rest\NodeInfo::init();
}
require_once \dirname( __FILE__ ) . '/includes/class-admin.php';
\Activitypub\Admin::init();
@ -72,7 +76,7 @@ function init() {
return '\Activitypub\Rest\Server';
} );
}
add_action( 'plugins_loaded', '\Activitypub\init' );
\add_action( 'plugins_loaded', '\Activitypub\init' );
/**
* Add rewrite rules

View File

@ -10,24 +10,23 @@ namespace Activitypub;
*/
class Activity_Dispatcher {
/**
* Initialize the class, registering WordPress hooks
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_action( 'activitypub_send_post_activity', array( '\Activitypub\Activity_Dispatcher', 'send_post_activity' ) );
\add_action( 'activitypub_send_update_activity', array( '\Activitypub\Activity_Dispatcher', 'send_update_activity' ) );
// \add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity' ) );
\add_action( 'activitypub_send_delete_activity', array( '\Activitypub\Activity_Dispatcher', 'send_delete_activity' ) );
}
/**
* Send "create" activities
* Send "create" activities.
*
* @param int $post_id
* @param \Activitypub\Model\Post $activitypub_post
*/
public static function send_post_activity( $post_id ) {
$post = \get_post( $post_id );
$user_id = $post->post_author;
public static function send_post_activity( $activitypub_post ) {
// get latest version of post
$user_id = $activitypub_post->get_post_author();
$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->to_array() );
@ -40,15 +39,14 @@ class Activity_Dispatcher {
}
/**
* Send "update" activities
* Send "update" activities.
*
* @param int $post_id
* @param \Activitypub\Model\Post $activitypub_post
*/
public static function send_update_activity( $post_id ) {
$post = \get_post( $post_id );
$user_id = $post->post_author;
public static function send_update_activity( $activitypub_post ) {
// get latest version of post
$user_id = $activitypub_post->get_post_author();
$activitypub_post = new \Activitypub\Model\Post( $post );
$activitypub_activity = new \Activitypub\Model\Activity( 'Update', \Activitypub\Model\Activity::TYPE_FULL );
$activitypub_activity->from_post( $activitypub_post->to_array() );
@ -61,15 +59,14 @@ class Activity_Dispatcher {
}
/**
* Send "delete" activities
* Send "delete" activities.
*
* @param int $post_id
* @param \Activitypub\Model\Post $activitypub_post
*/
public static function send_delete_activity( $post_id ) {
$post = \get_post( $post_id );
$user_id = $post->post_author;
public static function send_delete_activity( $activitypub_post ) {
// get latest version of post
$user_id = $activitypub_post->get_post_author();
$activitypub_post = new \Activitypub\Model\Post( $post );
$activitypub_activity = new \Activitypub\Model\Activity( 'Delete', \Activitypub\Model\Activity::TYPE_FULL );
$activitypub_activity->from_post( $activitypub_post->to_array() );

View File

@ -8,7 +8,7 @@ namespace Activitypub;
*/
class Activitypub {
/**
* Initialize the class, registering WordPress hooks
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_filter( 'template_include', array( '\Activitypub\Activitypub', 'render_json_template' ), 99 );
@ -27,11 +27,11 @@ class Activitypub {
}
/**
* Return a AS2 JSON version of an author, post or page
* Return a AS2 JSON version of an author, post or page.
*
* @param string $template the path to the template object
* @param string $template The path to the template object.
*
* @return string the new path to the JSON template
* @return string The new path to the JSON template.
*/
public static function render_json_template( $template ) {
if ( ! \is_author() && ! \is_singular() ) {
@ -63,7 +63,7 @@ class Activitypub {
return $json_template;
}
// accept header as an array
// Accept header as an array.
$accept = \explode( ',', \trim( $accept_header ) );
if (
@ -79,8 +79,7 @@ class Activitypub {
}
/**
* Add the 'photos' query variable so WordPress
* won't mangle it.
* Add the 'activitypub' query variable so WordPress won't mangle it.
*/
public static function add_query_vars( $vars ) {
$vars[] = 'activitypub';
@ -96,36 +95,40 @@ class Activitypub {
}
/**
* Schedule Activities
* Schedule Activities.
*
* @param int $post_id
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param WP_Post $post Post object.
*/
public static function schedule_post_activity( $new_status, $old_status, $post ) {
// do not send activities if post is password protected
// Do not send activities if post is password protected.
if ( \post_password_required( $post ) ) {
return;
}
// check if post-type supports ActivityPub
// Check if post-type supports ActivityPub.
$post_types = \get_post_types_by_support( 'activitypub' );
if ( ! \in_array( $post->post_type, $post_types, true ) ) {
return;
}
$activitypub_post = new \Activitypub\Model\Post( $post );
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
\wp_schedule_single_event( \time(), 'activitypub_send_post_activity', array( $post->ID ) );
\wp_schedule_single_event( \time(), 'activitypub_send_post_activity', array( $activitypub_post ) );
} elseif ( 'publish' === $new_status ) {
\wp_schedule_single_event( \time(), 'activitypub_send_update_activity', array( $post->ID ) );
\wp_schedule_single_event( \time(), 'activitypub_send_update_activity', array( $activitypub_post ) );
} elseif ( 'trash' === $new_status ) {
\wp_schedule_single_event( \time(), 'activitypub_send_delete_activity', array( get_permalink( $post ) ) );
\wp_schedule_single_event( \time(), 'activitypub_send_delete_activity', array( $activitypub_post ) );
}
}
/**
* Replaces the default avatar
* Replaces the default avatar.
*
* @param array $args Arguments passed to get_avatar_data(), after processing.
* @param int|string|object $id_or_email A user ID, email address, or comment object
* @param array $args Arguments passed to get_avatar_data(), after processing.
* @param int|string|object $id_or_email A user ID, email address, or comment object.
*
* @return array $args
*/
@ -145,7 +148,7 @@ class Activitypub {
return \apply_filters( 'get_avatar_data', $args, $id_or_email );
}
// check if comment has an avatar
// Check if comment has an avatar.
$avatar = self::get_avatar_url( $id_or_email->comment_ID );
if ( $avatar ) {
@ -163,8 +166,7 @@ class Activitypub {
}
/**
* Function to retrieve Avatar URL if stored in meta
*
* Function to retrieve Avatar URL if stored in meta.
*
* @param int|WP_Comment $comment
*

View File

@ -30,7 +30,7 @@ class Admin {
\add_action( 'load-' . $settings_page, array( '\Activitypub\Admin', 'add_settings_help_tab' ) );
$followers_list_page = \add_users_page( \__( 'Followers', 'activitypub' ), __( 'Followers (Fediverse)', 'activitypub' ), 'read', 'activitypub-followers-list', array( '\Activitypub\Admin', 'followers_list_page' ) );
$followers_list_page = \add_users_page( \__( 'Followers', 'activitypub' ), \__( 'Followers (Fediverse)', 'activitypub' ), 'read', 'activitypub-followers-list', array( '\Activitypub\Admin', 'followers_list_page' ) );
\add_action( 'load-' . $followers_list_page, array( '\Activitypub\Admin', 'add_followers_list_help_tab' ) );
}
@ -50,13 +50,13 @@ class Admin {
}
/**
* Register PubSubHubbub settings
* Register ActivityPub settings
*/
public static function register_settings() {
\register_setting(
'activitypub', 'activitypub_post_content_type', array(
'type' => 'string',
'description' => \__( 'Use title and link, summary or full content', 'activitypub' ),
'description' => \__( 'Use title and link, summary, full or custom content', 'activitypub' ),
'show_in_rest' => array(
'schema' => array(
'enum' => array( 'title', 'excerpt', 'content' ),
@ -65,6 +65,14 @@ class Admin {
'default' => 'content',
)
);
\register_setting(
'activitypub', 'activitypub_custom_post_content', array(
'type' => 'string',
'description' => \__( 'Define your own custom post template', 'activitypub' ),
'show_in_rest' => true,
'default' => ACTIVITYPUB_CUSTOM_POST_CONTENT,
)
);
\register_setting(
'activitypub', 'activitypub_object_type', array(
'type' => 'string',
@ -77,13 +85,6 @@ class Admin {
'default' => 'note',
)
);
\register_setting(
'activitypub', 'activitypub_use_shortlink', array(
'type' => 'boolean',
'description' => \__( 'Use the Shortlink instead of the permalink', 'activitypub' ),
'default' => 0,
)
);
\register_setting(
'activitypub', 'activitypub_use_hashtags', array(
'type' => 'boolean',
@ -92,10 +93,10 @@ class Admin {
)
);
\register_setting(
'activitypub', 'activitypub_add_tags_as_hashtags', array(
'type' => 'boolean',
'description' => \__( 'Add all tags as hashtags at the end of each activity', 'activitypub' ),
'default' => 0,
'activitypub', 'activitypub_allowed_html', array(
'type' => 'string',
'description' => \__( 'List of HTML elements that are allowed in activities.', 'activitypub' ),
'default' => ACTIVITYPUB_ALLOWED_HTML,
)
);
\register_setting(
@ -106,14 +107,6 @@ class Admin {
'default' => array( 'post', 'pages' ),
)
);
\register_setting(
'activitypub', 'activitypub_blacklist', array(
'type' => 'string',
'description' => \esc_html__( 'Block fediverse instances', 'activitypub' ),
'show_in_rest' => true,
'default' => 'gab.com',
)
);
}
public static function add_settings_help_tab() {

View File

@ -15,10 +15,6 @@ class Hashtag {
\add_filter( 'wp_insert_post', array( '\Activitypub\Hashtag', 'insert_post' ), 99, 2 );
\add_filter( 'the_content', array( '\Activitypub\Hashtag', 'the_content' ), 99, 2 );
}
if ( '1' === \get_option( 'activitypub_add_tags_as_hashtags', '0' ) ) {
\add_filter( 'activitypub_the_summary', array( '\Activitypub\Hashtag', 'add_hashtags_to_content' ), 10, 2 );
\add_filter( 'activitypub_the_content', array( '\Activitypub\Hashtag', 'add_hashtags_to_content' ), 10, 2 );
}
}
/**
@ -69,28 +65,4 @@ class Hashtag {
return '#' . $tag;
}
/**
* Adds all tags as hashtags to the post/summary content
*
* @param string $content
* @param WP_Post $post
*
* @return string
*/
public static function add_hashtags_to_content( $content, $post ) {
$tags = \get_the_tags( $post->ID );
if ( ! $tags ) {
return $content;
}
$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 $content . '<p>' . \implode( ' ', $hash_tags ) . '</p>';
}
}

View File

@ -8,5 +8,76 @@ namespace Activitypub;
*/
class Health_Check {
public static function init() {
\add_filter( 'site_status_tests', array( '\Activitypub\Health_Check', 'add_tests' ) );
}
public static function add_tests( $tests ) {
$tests['direct']['activitypub_test_profile_url'] = array(
'label' => \__( 'Profile URL test', 'activitypub' ),
'test' => array( '\Activitypub\Health_Check', 'test_profile_url' ),
);
//$tests['direct']['activitypub_test_profile_url2'] = array(
// 'label' => __( 'Profile URL Test', 'activitypub' ),
// 'test' => array( '\Activitypub\Health_Check', 'test_profile_url' ),
//);
return $tests;
}
public static function test_profile_url() {
$result = array(
'label' => \__( 'Profile URL accessible', 'activitypub' ),
'status' => 'good',
'badge' => array(
'label' => \__( 'ActivityPub', 'activitypub' ),
'color' => 'green',
),
'description' => \sprintf(
'<p>%s</p>',
\__( 'Your profile URL is accessible and do not redirect to the home page.', 'activitypub' )
),
'actions' => '',
'test' => 'test_profile_url',
);
$enum = self::is_profile_url_accessible();
if ( true !== $enum ) {
$result['status'] = 'critical';
$result['label'] = \__( 'Profile URL is not accessible', 'activitypub' );
$result['description'] = \sprintf(
'<p>%s</p>',
\__( 'Authorization Headers are being blocked by your hosting provider. This will cause IndieAuth to fail.', 'activitypub' )
);
}
return $result;
}
public static function is_profile_url_accessible() {
$user = \wp_get_current_user();
$author_url = \get_author_posts_url( $user->ID );
// check for "author" in URL
if ( false === \strpos( $author_url, 'author' ) ) {
return false;
}
// try to access author URL
$response = \wp_remote_get( $author_url, array( 'headers' => array( 'Accept' => 'application/activity+json' ) ) );
if ( \is_wp_error( $response ) ) {
return false;
}
// check if response is JSON
$body = \wp_remote_retrieve_body( $response );
if ( ! \is_string( $body ) || ! \is_array( \json_decode( $body, true ) ) ) {
return false;
}
return true;
}
}

View File

@ -53,7 +53,7 @@ class Signature {
$config = array(
'digest_alg' => 'sha512',
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
'private_key_type' => \OPENSSL_KEYTYPE_RSA,
);
$key = \openssl_pkey_new( $config );
@ -70,7 +70,7 @@ class Signature {
\update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] );
}
public static function generate_signature( $user_id, $url, $date ) {
public static function generate_signature( $user_id, $url, $date, $digest = null ) {
$key = self::get_private_key( $user_id );
$url_parts = \wp_parse_url( $url );
@ -88,18 +88,31 @@ class Signature {
$path .= '?' . $url_parts['query'];
}
$signed_string = "(request-target): post $path\nhost: $host\ndate: $date";
if ( ! empty( $digest ) ) {
$signed_string = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest";
} else {
$signed_string = "(request-target): post $path\nhost: $host\ndate: $date";
}
$signature = null;
\openssl_sign( $signed_string, $signature, $key, OPENSSL_ALGO_SHA256 );
\openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 );
$signature = \base64_encode( $signature ); // phpcs:ignore
$key_id = \get_author_posts_url( $user_id ) . '#main-key';
return \sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date",signature="%s"', $key_id, $signature );
if ( ! empty( $digest ) ) {
return \sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="%s"', $key_id, $signature );
} else {
return \sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date",signature="%s"', $key_id, $signature );
}
}
public static function verify_signature( $headers, $signature ) {
}
public static function generate_digest( $body ) {
$digest = \base64_encode( \hash( 'sha256', $body, true ) ); // phpcs:ignore
return "$digest";
}
}

View File

@ -23,7 +23,8 @@ function get_context() {
function safe_remote_post( $url, $body, $user_id ) {
$date = \gmdate( 'D, d M Y H:i:s T' );
$signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date );
$digest = \Activitypub\Signature::generate_digest( $body );
$signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date, $digest );
$wp_version = \get_bloginfo( 'version' );
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
@ -35,6 +36,7 @@ function safe_remote_post( $url, $body, $user_id ) {
'headers' => array(
'Accept' => 'application/activity+json',
'Content-Type' => 'application/activity+json',
'Digest' => "SHA-256=$digest",
'Signature' => $signature,
'Date' => $date,
),
@ -89,7 +91,7 @@ function get_webfinger_resource( $user_id ) {
$user = \get_user_by( 'id', $user_id );
return $user->user_login . '@' . \wp_parse_url( \home_url(), PHP_URL_HOST );
return $user->user_login . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
}
/**
@ -157,7 +159,7 @@ function get_inbox_by_actor( $actor ) {
return $metadata['inbox'];
}
return new \WP_Error( 'activitypub_no_inbox', __( 'No "Inbox" found', 'activitypub' ), $metadata );
return new \WP_Error( 'activitypub_no_inbox', \__( 'No "Inbox" found', 'activitypub' ), $metadata );
}
/**
@ -253,13 +255,13 @@ function url_to_authorid( $url ) {
global $wp_rewrite;
// check if url hase the same host
if ( wp_parse_url( site_url(), PHP_URL_HOST ) !== wp_parse_url( $url, PHP_URL_HOST ) ) {
if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) !== \wp_parse_url( $url, \PHP_URL_HOST ) ) {
return 0;
}
// first, check to see if there is a 'author=N' to match against
if ( \preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) {
$id = absint( $values[1] );
$id = \absint( $values[1] );
if ( $id ) {
return $id;
}
@ -279,7 +281,7 @@ function url_to_authorid( $url ) {
// match the rewrite rule with the passed url
if ( \preg_match( '/https?:\/\/(.+)' . \preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) {
$user = get_user_by( 'slug', $match[2] );
$user = \get_user_by( 'slug', $match[2] );
if ( $user ) {
return $user->ID;
}
@ -287,48 +289,3 @@ function url_to_authorid( $url ) {
return 0;
}
/**
* Get the blacklist from the WordPress options table
*
* @return array the list of blacklisted hosts
*
* @uses apply_filters() Calls 'activitypub_blacklist' filter
*/
function get_blacklist() {
$blacklist = \get_option( 'activitypub_blacklist' );
$blacklist_hosts = \explode( PHP_EOL, $blacklist );
// if no values have been set, revert to the defaults
if ( ! $blacklist || ! $blacklist_hosts || ! \is_array( $blacklist_hosts ) ) {
$blacklist_hosts = array();
}
// clean out any blank values
foreach ( $blacklist_hosts as $key => $value ) {
if ( empty( $value ) ) {
unset( $blacklist_hosts[ $key ] );
} else {
$blacklist_hosts[ $key ] = \trim( $blacklist_hosts[ $key ] );
}
}
return \apply_filters( 'activitypub_blacklist', $blacklist_hosts );
}
/**
* Check if an URL is blacklisted
*
* @param string $url an URL to check
*
* @return boolean
*/
function is_blacklisted( $url ) {
foreach ( \ActivityPub\get_blacklist() as $blacklisted_host ) {
if ( \strpos( $url, $blacklisted_host ) !== false ) {
return true;
}
}
return false;
}

View File

@ -47,15 +47,31 @@ class Activity {
public function from_post( $object ) {
$this->object = $object;
$this->published = $object['published'];
$this->actor = $object['attributedTo'];
$this->id = $object['id'];
if ( isset( $object['published'] ) ) {
$this->published = $object['published'];
}
if ( isset( $object['attributedTo'] ) ) {
$this->actor = $object['attributedTo'];
}
if ( isset( $object['id'] ) ) {
$this->id = $object['id'];
}
}
public function from_comment( $object ) {
}
public function to_comment() {
}
public function from_remote_array( $array ) {
}
public function to_array() {
$array = \get_object_vars( $this );
@ -68,8 +84,13 @@ class Activity {
return $array;
}
/**
* Convert to JSON
*
* @return void
*/
public function to_json() {
return \wp_json_encode( $this->to_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT );
return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
}
public function to_simple_array() {
@ -90,6 +111,6 @@ class Activity {
}
public function to_simple_json() {
return \wp_json_encode( $this->to_simple_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT );
return \wp_json_encode( $this->to_simple_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
}
}

View File

@ -8,55 +8,74 @@ namespace Activitypub\Model;
*/
class Post {
private $post;
/**
* Initialize the class, registering WordPress hooks
*/
public static function init() {
\add_filter( 'activitypub_the_summary', array( '\Activitypub\Model\Post', 'add_backlink_to_content' ), 15, 2 );
\add_filter( 'activitypub_the_content', array( '\Activitypub\Model\Post', 'add_backlink_to_content' ), 15, 2 );
}
private $post_author;
private $id;
private $summary;
private $content;
private $attachments;
private $tags;
private $object_type;
public function __construct( $post = null ) {
$this->post = \get_post( $post );
$this->post_author = $this->post->post_author;
$this->id = $this->generate_id();
$this->summary = $this->generate_the_title();
$this->content = $this->generate_the_content();
$this->attachments = $this->generate_attachments();
$this->tags = $this->generate_tags();
$this->object_type = $this->generate_object_type();
}
public function get_post() {
return $this->post;
}
public function __call( $method, $params ) {
$var = \strtolower( \substr( $method, 4 ) );
public function get_post_author() {
return $this->post->post_author;
if ( \strncasecmp( $method, 'get', 3 ) === 0 ) {
return $this->$var;
}
if ( \strncasecmp( $method, 'set', 3 ) === 0 ) {
$this->$var = $params[0];
}
}
public function to_array() {
$post = $this->post;
$array = array(
'id' => \get_permalink( $post ),
'type' => $this->get_object_type(),
'published' => \date( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_date ) ),
'id' => $this->id,
'type' => $this->object_type,
'published' => \date( 'Y-m-d\TH:i:s\Z', \strtotime( $post->post_date_gmt ) ),
'attributedTo' => \get_author_posts_url( $post->post_author ),
'summary' => $this->get_the_title(),
'summary' => $this->summary,
'inReplyTo' => null,
'content' => $this->get_the_content(),
'content' => $this->content,
'contentMap' => array(
\strstr( \get_locale(), '_', true ) => $this->get_the_content(),
\strstr( \get_locale(), '_', true ) => $this->content,
),
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
'cc' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
'attachment' => $this->get_attachments(),
'tag' => $this->get_tags(),
'attachment' => $this->attachments,
'tag' => $this->tags,
);
return \apply_filters( 'activitypub_post', $array );
}
public function to_json() {
return \wp_json_encode( $this->to_array(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT );
return \wp_json_encode( $this->to_array(), \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT );
}
public function get_attachments() {
public function generate_id() {
$post = $this->post;
$permalink = \get_permalink( $post );
// replace 'trashed' for delete activity
return \str_replace( '__trashed', '', $permalink );
}
public function generate_attachments() {
$max_images = \apply_filters( 'activitypub_max_images', 3 );
$images = array();
@ -104,7 +123,7 @@ class Post {
$image = array(
'type' => 'Image',
'url' => $thumbnail[0],
'mediaType' => $mimetype
'mediaType' => $mimetype,
);
if ( $alt ) {
$image['name'] = $alt;
@ -116,7 +135,7 @@ class Post {
return $images;
}
public function get_tags() {
public function generate_tags() {
$tags = array();
$post_tags = \get_the_tags( $this->post->ID );
@ -142,7 +161,7 @@ class Post {
*
* @return string the object-type
*/
public function get_object_type() {
public function generate_object_type() {
if ( 'wordpress-post-format' !== \get_option( 'activitypub_object_type', 'note' ) ) {
return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) );
}
@ -199,26 +218,47 @@ class Post {
return $object_type;
}
public function get_the_content() {
public function generate_the_content() {
$post = $this->post;
$content = $this->get_post_content_template();
$content = \str_replace( '%title%', \get_the_title( $post->ID ), $content );
$content = \str_replace( '%excerpt%', $this->get_the_post_excerpt(), $content );
$content = \str_replace( '%content%', $this->get_the_post_content(), $content );
$content = \str_replace( '%permalink%', $this->get_the_post_link( 'permalink' ), $content );
$content = \str_replace( '%shortlink%', $this->get_the_post_link( 'shortlink' ), $content );
$content = \str_replace( '%hashtags%', $this->get_the_post_hashtags(), $content );
// backwards compatibility
$content = \str_replace( '%tags%', $this->get_the_post_hashtags(), $content );
$content = \trim( \preg_replace( '/[\r\n]{2,}/', '', $content ) );
$filtered_content = \apply_filters( 'activitypub_the_content', $content, $this->post );
$decoded_content = \html_entity_decode( $filtered_content, \ENT_QUOTES, 'UTF-8' );
$allowed_html = \apply_filters( 'activitypub_allowed_html', \get_option( 'activitypub_allowed_html', ACTIVITYPUB_ALLOWED_HTML ) );
if ( $allowed_html ) {
return \strip_tags( $decoded_content, $allowed_html );
}
return $decoded_content;
}
public function get_post_content_template() {
if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
return $this->get_the_post_summary();
return "%excerpt%\n\n<p>%permalink%</p>";
}
if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
return $this->get_the_title();
return "<p><strong>%title%</strong></p>\n\n<p>%permalink%</p>";
}
return $this->get_the_post_content();
}
public function get_the_title() {
if ( 'Article' === $this->get_object_type() ) {
$title = \get_the_title( $this->post );
return \html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
if ( 'content' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
return "%content%\n\n<p>%hashtags%</p>\n\n<p>%permalink%</p>";
}
return null;
return \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
}
/**
@ -255,7 +295,7 @@ class Post {
}
}
return $excerpt;
return \apply_filters( 'the_excerpt', $excerpt );
}
/**
@ -268,34 +308,7 @@ class Post {
$content = \get_post_field( 'post_content', $post );
$filtered_content = \apply_filters( 'the_content', $content );
$filtered_content = \apply_filters( 'activitypub_the_content', $filtered_content, $this->post );
$decoded_content = \html_entity_decode( $filtered_content, ENT_QUOTES, 'UTF-8' );
$allowed_html = \apply_filters( 'activitypub_allowed_html', '<a><p><ul><ol><li><code><blockquote><pre>' );
return \trim( \preg_replace( '/[\r\n]{2,}/', '', \strip_tags( $decoded_content, $allowed_html ) ) );
}
/**
* Get the excerpt for a post for use outside of the loop.
*
* @param int Optional excerpt length.
*
* @return string The excerpt.
*/
public function get_the_post_summary( $summary_length = 400 ) {
$summary = $this->get_the_post_excerpt( $summary_length );
$filtered_summary = \apply_filters( 'the_excerpt', $summary );
$filtered_summary = \apply_filters( 'activitypub_the_summary', $filtered_summary, $this->post );
$decoded_summary = \html_entity_decode( $filtered_summary, ENT_QUOTES, 'UTF-8' );
$allowed_html = \apply_filters( 'activitypub_allowed_html', '<a><p>' );
return \trim( \preg_replace( '/[\r\n]{2,}/', '', \strip_tags( $decoded_summary, $allowed_html ) ) );
return \apply_filters( 'the_content', $content );
}
/**
@ -306,15 +319,42 @@ class Post {
*
* @return string
*/
public static function add_backlink_to_content( $content, $post ) {
$link = '';
public function get_the_post_link( $type = 'permalink' ) {
$post = $this->post;
if ( \get_option( 'activitypub_use_shortlink', 0 ) ) {
if ( 'shortlink' === $type ) {
$link = \esc_url( \wp_get_shortlink( $post->ID ) );
} else {
} elseif ( 'permalink' === $type ) {
$link = \esc_url( \get_permalink( $post->ID ) );
} else {
return '';
}
return $content . '<p><a href="' . $link . '">' . $link . '</a></p>';
return \sprintf( '<a href="%1$s">%1$s</a>', $link );
}
/**
* Adds all tags as hashtags to the post/summary content
*
* @param string $content
* @param WP_Post $post
*
* @return string
*/
public function get_the_post_hashtags() {
$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 );
}
}

View File

@ -21,7 +21,7 @@ class Followers {
isset( $follower['type'] ) &&
'Person' === $follower['type'] &&
isset( $follower['id'] ) &&
false !== \filter_var( $follower['id'], FILTER_VALIDATE_URL )
false !== \filter_var( $follower['id'], \FILTER_VALIDATE_URL )
) {
$followers[ $key ] = $follower['id'];
}
@ -45,7 +45,7 @@ class Followers {
isset( $actor['type'] ) &&
'Person' === $actor['type'] &&
isset( $actor['id'] ) &&
false !== \filter_var( $actor['id'], FILTER_VALIDATE_URL )
false !== \filter_var( $actor['id'], \FILTER_VALIDATE_URL )
) {
$actor = $actor['id'];
}

View File

@ -0,0 +1,67 @@
<?php
namespace Activitypub\Peer;
/**
* ActivityPub Users DB-Class
*
* @author Matthias Pfefferle
*/
class Users {
/**
* Undocumented function
*
* @return void
*/
public static function get_user_by_various( $data ) {
}
/**
* Examine a url and try to determine the author ID it represents.
*
* Checks are supposedly from the hosted site blog.
*
* @param string $url Permalink to check.
*
* @return int User ID, or 0 on failure.
*/
public static function url_to_authorid( $url ) {
global $wp_rewrite;
// check if url hase the same host
if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) !== \wp_parse_url( $url, \PHP_URL_HOST ) ) {
return 0;
}
// first, check to see if there is a 'author=N' to match against
if ( \preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) {
$id = \absint( $values[1] );
if ( $id ) {
return $id;
}
}
// check to see if we are using rewrite rules
$rewrite = $wp_rewrite->wp_rewrite_rules();
// not using rewrite rules, and 'author=N' method failed, so we're out of options
if ( empty( $rewrite ) ) {
return 0;
}
// generate rewrite rule for the author url
$author_rewrite = $wp_rewrite->get_author_permastruct();
$author_regexp = \str_replace( '%author%', '', $author_rewrite );
// match the rewrite rule with the passed url
if ( \preg_match( '/https?:\/\/(.+)' . \preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) {
$user = \get_user_by( 'slug', $match[2] );
if ( $user ) {
return $user->ID;
}
}
return 0;
}
}

View File

@ -23,9 +23,10 @@ class Followers {
\register_rest_route(
'activitypub/1.0', '/users/(?P<id>\d+)/followers', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Followers', 'get' ),
'args' => self::request_parameters(),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Followers', 'get' ),
'args' => self::request_parameters(),
'permission_callback' => '__return_true',
),
)
);

View File

@ -23,9 +23,10 @@ class Following {
\register_rest_route(
'activitypub/1.0', '/users/(?P<id>\d+)/following', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Following', 'get' ),
'args' => self::request_parameters(),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Following', 'get' ),
'args' => self::request_parameters(),
'permission_callback' => '__return_true',
),
)
);

View File

@ -16,7 +16,7 @@ class Inbox {
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Inbox', 'register_routes' ) );
\add_filter( 'rest_pre_serve_request', array( '\Activitypub\Rest\Inbox', 'serve_request' ), 11, 4 );
\add_action( 'activitypub_inbox_follow', array( '\Activitypub\Rest\Inbox', 'handle_follow' ), 10, 2 );
\add_action( 'activitypub_inbox_unfollow', array( '\Activitypub\Rest\Inbox', 'handle_unfollow' ), 10, 2 );
\add_action( 'activitypub_inbox_undo', array( '\Activitypub\Rest\Inbox', 'handle_unfollow' ), 10, 2 );
//\add_action( 'activitypub_inbox_like', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
//\add_action( 'activitypub_inbox_announce', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
\add_action( 'activitypub_inbox_create', array( '\Activitypub\Rest\Inbox', 'handle_create' ), 10, 2 );
@ -29,8 +29,10 @@ class Inbox {
\register_rest_route(
'activitypub/1.0', '/inbox', array(
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox' ),
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox' ),
'args' => self::shared_inbox_request_parameters(),
'permission_callback' => '__return_true',
),
)
);
@ -38,9 +40,10 @@ class Inbox {
\register_rest_route(
'activitypub/1.0', '/users/(?P<user_id>\d+)/inbox', array(
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox' ),
'args' => self::request_parameters(),
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox' ),
'args' => self::user_inbox_request_parameters(),
'permission_callback' => '__return_true',
),
)
);
@ -97,12 +100,20 @@ class Inbox {
/**
* The shared inbox
*
* @param [type] $request [description]
* @param WP_REST_Request $request
*
* @return WP_Error not yet implemented
* @return WP_REST_Response
*/
public static function shared_inbox( $request ) {
$data = $request->get_params();
$type = \strtoloer( $request->get_param( 'type' ) );
foreach ( $users as $user ) {
\do_action( 'activitypub_inbox', $data, $user_id, $type );
\do_action( "activitypub_inbox_{$type}", $data, $user_id );
}
return new \WP_REST_Response( array(), 202 );
}
/**
@ -110,7 +121,7 @@ class Inbox {
*
* @return array list of parameters
*/
public static function request_parameters() {
public static function user_inbox_request_parameters() {
$params = array();
$params['page'] = array(
@ -124,25 +135,56 @@ class Inbox {
$params['id'] = array(
'required' => true,
'type' => 'string',
'validate_callback' => function( $param, $request, $key ) {
'sanitize_callback' => 'esc_url_raw',
);
$params['actor'] = array(
'required' => true,
'sanitize_callback' => function( $param, $request, $key ) {
if ( ! \is_string( $param ) ) {
$param = $param['id'];
}
return ! \Activitypub\is_blacklisted( $param );
return \esc_url_raw( $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,
);
return $params;
}
/**
* The supported parameters
*
* @return array list of parameters
*/
public static function shared_inbox_request_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' ),
'validate_callback' => function( $param, $request, $key ) {
if ( ! \is_string( $param ) ) {
$param = $param['id'];
}
return ! \Activitypub\is_blacklisted( $param );
},
'sanitize_callback' => function( $param, $request, $key ) {
if ( ! \is_string( $param ) ) {
$param = $param['id'];
@ -165,6 +207,37 @@ class Inbox {
//'type' => 'object',
);
$params['to'] = array(
'required' => true,
'sanitize_callback' => function( $param, $request, $key ) {
if ( \is_string( $param ) ) {
$param = array( $param );
}
return $param;
},
);
$params['cc'] = array(
'sanitize_callback' => function( $param, $request, $key ) {
if ( \is_string( $param ) ) {
$param = array( $param );
}
return $param;
},
);
$params['bcc'] = array(
'sanitize_callback' => function( $param, $request, $key ) {
if ( \is_string( $param ) ) {
$param = array( $param );
}
return $param;
},
);
return $params;
}
@ -186,7 +259,7 @@ class Inbox {
$activity->set_object( $object );
$activity->set_actor( \get_author_posts_url( $user_id ) );
$activity->set_to( $object['actor'] );
$activity->set_id( \get_author_posts_url( $user_id ) . '#follow' . \preg_replace( '~^https?://~', '', $object['actor'] ) );
$activity->set_id( \get_author_posts_url( $user_id ) . '#follow-' . \preg_replace( '~^https?://~', '', $object['actor'] ) );
$activity = $activity->to_simple_json();
@ -200,7 +273,9 @@ class Inbox {
* @param int $user_id The id of the local blog-user
*/
public static function handle_unfollow( $object, $user_id ) {
\Activitypub\Peer\Followers::remove_follower( $object['actor'], $user_id );
if ( isset( $object['object'] ) && isset( $object['object']['type'] ) && 'Follow' === $object['object']['type'] ) {
\Activitypub\Peer\Followers::remove_follower( $object['actor'], $user_id );
}
}
/**
@ -212,8 +287,15 @@ class Inbox {
public static function handle_reaction( $object, $user_id ) {
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
$comment_post_id = \url_to_postid( $object['object'] );
// save only replys and reactions
if ( ! $comment_post_id ) {
return false;
}
$commentdata = array(
'comment_post_ID' => \url_to_postid( $object['object'] ),
'comment_post_ID' => $comment_post_id,
'comment_author' => \esc_attr( $meta['name'] ),
'comment_author_email' => '',
'comment_author_url' => \esc_url_raw( $object['actor'] ),
@ -227,13 +309,12 @@ class Inbox {
),
);
// disable flood control
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
// do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' );
$state = \wp_new_comment( $commentdata, true );
// re-add flood control
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
\remove_filter( 'pre_option_require_name_email', '__return_false' );
}
/**
@ -245,8 +326,15 @@ class Inbox {
public static function handle_create( $object, $user_id ) {
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
$comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
// save only replys and reactions
if ( ! $comment_post_id ) {
return false;
}
$commentdata = array(
'comment_post_ID' => \url_to_postid( $object['object']['inReplyTo'] ),
'comment_post_ID' => $comment_post_id,
'comment_author' => \esc_attr( $meta['name'] ),
'comment_author_url' => \esc_url_raw( $object['actor'] ),
'comment_content' => \wp_filter_kses( $object['object']['content'] ),
@ -260,12 +348,11 @@ class Inbox {
),
);
// disable flood control
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
// do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' );
$state = \wp_new_comment( $commentdata, true );
// re-add flood control
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
\remove_filter( 'pre_option_require_name_email', '__return_false' );
}
}

View File

@ -25,8 +25,9 @@ class Nodeinfo {
\register_rest_route(
'activitypub/1.0', '/nodeinfo/discovery', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'discovery' ),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'discovery' ),
'permission_callback' => '__return_true',
),
)
);
@ -34,8 +35,9 @@ class Nodeinfo {
\register_rest_route(
'activitypub/1.0', '/nodeinfo', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo' ),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo' ),
'permission_callback' => '__return_true',
),
)
);
@ -43,8 +45,9 @@ class Nodeinfo {
\register_rest_route(
'activitypub/1.0', '/nodeinfo2', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo2' ),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Nodeinfo', 'nodeinfo2' ),
'permission_callback' => '__return_true',
),
)
);
@ -105,7 +108,7 @@ class Nodeinfo {
$nodeinfo['version'] = '1.0';
$nodeinfo['server'] = array(
'baseUrl' => home_url( '/' ),
'baseUrl' => \home_url( '/' ),
'name' => \get_bloginfo( 'name' ),
'software' => 'wordpress',
'version' => \get_bloginfo( 'version' ),

View File

@ -16,9 +16,10 @@ class Ostatus {
\register_rest_route(
'activitypub/1.0', '/ostatus/remote-follow', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Ostatus', 'get' ),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Ostatus', 'get' ),
// 'args' => self::request_parameters(),
'permission_callback' => '__return_true',
),
)
);

View File

@ -23,9 +23,10 @@ class Outbox {
\register_rest_route(
'activitypub/1.0', '/users/(?P<id>\d+)/outbox', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox' ),
'args' => self::request_parameters(),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Outbox', 'user_outbox' ),
'args' => self::request_parameters(),
'permission_callback' => '__return_true',
),
)
);
@ -42,7 +43,7 @@ class Outbox {
$author = \get_user_by( 'ID', $user_id );
if ( ! $author ) {
return new \WP_Error( 'rest_invalid_param', __( 'User not found', 'activitypub' ), array(
return new \WP_Error( 'rest_invalid_param', \__( 'User not found', 'activitypub' ), array(
'status' => 404,
'params' => array(
'user_id' => \__( 'User not found', 'activitypub' ),

View File

@ -18,13 +18,17 @@ class Server extends \WP_REST_Server {
public function dispatch( $request ) {
$content_type = $request->get_content_type();
if ( ! $content_type ) {
return parent::dispatch( $request );
}
// check for content-sub-types like 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
if ( \preg_match( '/application\/([a-zA-Z+_-]+\+)json/', $content_type['value'] ) ) {
$request->set_header( 'Content-Type', 'application/json' );
}
// make request filterable
$request = apply_filters( 'activitypub_pre_dispatch_request', $request );
$request = \apply_filters( 'activitypub_pre_dispatch_request', $request );
return parent::dispatch( $request );
}

View File

@ -24,9 +24,10 @@ class Webfinger {
\register_rest_route(
'activitypub/1.0', '/webfinger', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Webfinger', 'webfinger' ),
'args' => self::request_parameters(),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( '\Activitypub\Rest\Webfinger', 'webfinger' ),
'args' => self::request_parameters(),
'permission_callback' => '__return_true',
),
)
);
@ -51,7 +52,7 @@ class Webfinger {
$resource_identifier = $matches[1];
$resource_host = $matches[2];
if ( \wp_parse_url( \home_url( '/' ), PHP_URL_HOST ) !== $resource_host ) {
if ( \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) !== $resource_host ) {
return new \WP_Error( 'activitypub_wrong_host', \__( 'Resource host does not match blog host', 'activitypub' ), array( 'status' => 404 ) );
}

View File

@ -2,10 +2,10 @@
# This file is distributed under the MIT.
msgid ""
msgstr ""
"Project-Id-Version: ActivityPub 0.10.1\n"
"Project-Id-Version: ActivityPub 0.12.0\n"
"Report-Msgid-Bugs-To: "
"https://wordpress.org/support/plugin/wordpress-activitypub\n"
"POT-Creation-Date: 2020-05-03 22:06:03+00:00\n"
"POT-Creation-Date: 2020-12-21 19:48:46+00:00\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -14,276 +14,6 @@ msgstr ""
"Language-Team: LANGUAGE <LL@li.org>\n"
"X-Generator: grunt-wp-i18n 1.0.3\n"
#: includes/class-admin.php:33
msgid "Followers"
msgstr ""
#: includes/class-admin.php:33 templates/followers-list.php:2
msgid "Followers (Fediverse)"
msgstr ""
#: includes/class-admin.php:59
msgid "Use title and link, summary or full content"
msgstr ""
#: includes/class-admin.php:71
msgid "The Activity-Object-Type"
msgstr ""
#: includes/class-admin.php:83 templates/settings.php:36
msgid "Use the Shortlink instead of the permalink"
msgstr ""
#: includes/class-admin.php:90
msgid ""
"Add hashtags in the content as native tags and replace the #tag with the "
"tag-link"
msgstr ""
#: includes/class-admin.php:97
msgid "Add all tags as hashtags at the end of each activity"
msgstr ""
#: includes/class-admin.php:104
msgid "Enable ActivityPub support for post types"
msgstr ""
#: includes/class-admin.php:112
msgid "Block fediverse instances"
msgstr ""
#: includes/class-admin.php:123
msgid "Overview"
msgstr ""
#: includes/class-admin.php:125
msgid ""
"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."
msgstr ""
#: includes/class-admin.php:130
msgid "For more information:"
msgstr ""
#: includes/class-admin.php:131
msgid "<a href=\"https://activitypub.rocks/\">Test Suite</a>"
msgstr ""
#: includes/class-admin.php:132
msgid "<a href=\"https://www.w3.org/TR/activitypub/\">W3C Spec</a>"
msgstr ""
#: includes/class-admin.php:133
msgid ""
"<a href=\"https://github.com/pfefferle/wordpress-activitypub/issues\">Give "
"us feedback</a>"
msgstr ""
#: includes/class-admin.php:135
msgid "<a href=\"https://notiz.blog/donate\">Donate</a>"
msgstr ""
#: includes/class-admin.php:145
msgid "Fediverse"
msgstr ""
#: includes/functions.php:110
msgid "The \"actor\" is no valid URL"
msgstr ""
#: includes/functions.php:132
msgid "No valid JSON data"
msgstr ""
#: includes/functions.php:160
msgid "No \"Inbox\" found"
msgstr ""
#: includes/functions.php:186
msgid "No \"Public-Key\" found"
msgstr ""
#: includes/functions.php:214
msgid "Profile identifier"
msgstr ""
#: includes/functions.php:219
#. translators: the webfinger resource
msgid "Try to follow \"@%s\" in the Mastodon/Friendica search field."
msgstr ""
#: includes/peer/class-followers.php:53
msgid "Unknown Actor schema"
msgstr ""
#: includes/rest/class-followers.php:46 includes/rest/class-followers.php:49
#: includes/rest/class-following.php:46 includes/rest/class-following.php:49
#: includes/rest/class-outbox.php:45 includes/rest/class-outbox.php:48
#: includes/rest/class-webfinger.php:61
msgid "User not found"
msgstr ""
#: includes/rest/class-webfinger.php:48
msgid "Resource is invalid"
msgstr ""
#: includes/rest/class-webfinger.php:55
msgid "Resource host does not match blog host"
msgstr ""
#: includes/table/followers-list.php:11
msgid "Identifier"
msgstr ""
#: templates/followers-list.php:4
msgid "You currently have %s followers."
msgstr ""
#: templates/json-author.php:48
msgid "Blog"
msgstr ""
#: templates/json-author.php:58
msgid "Profile"
msgstr ""
#: templates/json-author.php:69
msgid "Website"
msgstr ""
#: templates/settings.php:2
msgid "ActivityPub Settings"
msgstr ""
#: templates/settings.php:4
msgid ""
"ActivityPub turns your blog into a federated social network. This means you "
"can share and talk to everyone using the ActivityPub protocol, including "
"users of Friendica, Pleroma and Mastodon."
msgstr ""
#: templates/settings.php:9
msgid "Activities"
msgstr ""
#: templates/settings.php:11
msgid "All activity related settings."
msgstr ""
#: templates/settings.php:17
msgid "Post-Content"
msgstr ""
#: templates/settings.php:21
msgid "Title and link"
msgstr ""
#: templates/settings.php:21
msgid "Only the title and a link."
msgstr ""
#: templates/settings.php:24
msgid "Excerpt"
msgstr ""
#: templates/settings.php:24
msgid "A content summary, shortened to 400 characters and without markup."
msgstr ""
#: templates/settings.php:27
msgid "Content (default)"
msgstr ""
#: templates/settings.php:27
msgid "The full content."
msgstr ""
#: templates/settings.php:33
msgid "Backlink"
msgstr ""
#: templates/settings.php:42
msgid "Activity-Object-Type"
msgstr ""
#: templates/settings.php:46
msgid "Note (default)"
msgstr ""
#: templates/settings.php:46
msgid "Should work with most platforms."
msgstr ""
#: templates/settings.php:49
msgid "Article"
msgstr ""
#: templates/settings.php:49
msgid ""
"The presentation of the \"Article\" might change on different platforms. "
"Mastodon for example shows the \"Article\" type as a simple link."
msgstr ""
#: templates/settings.php:52
msgid "WordPress Post-Format"
msgstr ""
#: templates/settings.php:52
msgid "Maps the WordPress Post-Format to the ActivityPub Object Type."
msgstr ""
#: templates/settings.php:57
msgid "Supported post types"
msgstr ""
#: templates/settings.php:60
msgid "Enable ActivityPub support for the following post types:"
msgstr ""
#: templates/settings.php:77
msgid "Hashtags"
msgstr ""
#: templates/settings.php:81
msgid ""
"Add hashtags in the content as native tags and replace the "
"<code>#tag</code> with the tag-link."
msgstr ""
#: templates/settings.php:84
msgid "Add all tags as hashtags to the end of each activity."
msgstr ""
#: templates/settings.php:93
msgid "Server"
msgstr ""
#: templates/settings.php:95
msgid "Server related settings."
msgstr ""
#: templates/settings.php:106
msgid "Blacklist"
msgstr ""
#: templates/settings.php:110
msgid ""
"A list of hosts, you want to block, one host per line. Please use only the "
"host/domain of the server you want to block, without <code>http://</code> "
"and without <code>www.</code>. For example <code>example.com</code>."
msgstr ""
#: templates/settings.php:124
msgid ""
"If you like this plugin, what about a small <a "
"href=\"https://notiz.blog/donate\">donation</a>?"
msgstr ""
#. Plugin Name of the plugin/theme
msgid "ActivityPub"
msgstr ""

View File

@ -1,10 +1,10 @@
=== ActivityPub ===
Contributors: pfefferle
Contributors: pfefferle, mediaformat
Donate link: https://notiz.blog/donate/
Tags: OStatus, fediverse, activitypub, activitystream
Requires at least: 4.7
Tested up to: 5.4.1
Stable tag: 0.10.1
Tested up to: 5.6
Stable tag: 0.12.0
Requires PHP: 5.6
License: MIT
License URI: http://opensource.org/licenses/MIT
@ -88,6 +88,29 @@ Where 'blog' is the path to the subdirectory at which your blog resides.
Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
= 0.12.0 =
* use "pre_option_require_name_email" filter instead of "check_comment_flood". props [@akirk](https://github.com/akirk)
* save only comments/replies
* check for an explicit "undo -> follow" action. see https://wordpress.org/support/topic/qs-after-latest/
= 0.11.2 =
* fix inconsistent `%tags%` placeholder
= 0.11.1 =
* fix follow/unfollow actions
= 0.11.0 =
* add support for customizable post-content
* first try of a delete activity
* do not require email for AP entries. props [@akirk](https://github.com/akirk)
* fix [timezones](https://github.com/pfefferle/wordpress-activitypub/issues/63) bug. props [@mediaformat](https://github.com/mediaformat)
* fix [digest header](https://github.com/pfefferle/wordpress-activitypub/issues/104) bug. props [@mediaformat](https://github.com/mediaformat)
= 0.10.1 =
* fix inbox activities, like follow

View File

@ -9,7 +9,7 @@ $json->type = 'Person';
$json->name = \get_the_author_meta( 'display_name', $author_id );
$json->summary = \html_entity_decode(
\get_the_author_meta( 'description', $author_id ),
ENT_QUOTES,
\ENT_QUOTES,
'UTF-8'
);
$json->preferredUsername = \get_the_author_meta( 'login', $author_id ); // phpcs:ignore
@ -31,7 +31,7 @@ $json->outbox = \get_rest_url( null, "/activitypub/1.0/users/$author_id/outbox"
$json->followers = \get_rest_url( null, "/activitypub/1.0/users/$author_id/followers" );
$json->following = \get_rest_url( null, "/activitypub/1.0/users/$author_id/following" );
$json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_approves_followers', __return_false() ); // phpcs:ignore
$json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_approves_followers', \__return_false() ); // phpcs:ignore
// phpcs:ignore
$json->publicKey = array(
@ -45,20 +45,20 @@ $json->attachment = array();
$json->attachment[] = array(
'type' => 'PropertyValue',
'name' => __( 'Blog', 'activitypub' ),
'name' => \__( 'Blog', 'activitypub' ),
'value' => \html_entity_decode(
'<a rel="me" title="' . \esc_attr( \home_url( '/' ) ) . '" target="_blank" href="' . \home_url( '/' ) . '">' . \wp_parse_url( \home_url( '/' ), PHP_URL_HOST ) . '</a>',
ENT_QUOTES,
'<a rel="me" title="' . \esc_attr( \home_url( '/' ) ) . '" target="_blank" href="' . \home_url( '/' ) . '">' . \wp_parse_url( \home_url( '/' ), \PHP_URL_HOST ) . '</a>',
\ENT_QUOTES,
'UTF-8'
),
);
$json->attachment[] = array(
'type' => 'PropertyValue',
'name' => __( 'Profile', 'activitypub' ),
'name' => \__( 'Profile', 'activitypub' ),
'value' => \html_entity_decode(
'<a rel="me" title="' . \esc_attr( \get_author_posts_url( $author_id ) ) . '" target="_blank" href="' . \get_author_posts_url( $author_id ) . '">' . \wp_parse_url( \get_author_posts_url( $author_id ), PHP_URL_HOST ) . '</a>',
ENT_QUOTES,
'<a rel="me" title="' . \esc_attr( \get_author_posts_url( $author_id ) ) . '" target="_blank" href="' . \get_author_posts_url( $author_id ) . '">' . \wp_parse_url( \get_author_posts_url( $author_id ), \PHP_URL_HOST ) . '</a>',
\ENT_QUOTES,
'UTF-8'
),
);
@ -66,10 +66,10 @@ $json->attachment[] = array(
if ( \get_the_author_meta( 'user_url', $author_id ) ) {
$json->attachment[] = array(
'type' => 'PropertyValue',
'name' => __( 'Website', 'activitypub' ),
'name' => \__( 'Website', 'activitypub' ),
'value' => \html_entity_decode(
'<a rel="me" title="' . \esc_attr( \get_the_author_meta( 'user_url', $author_id ) ) . '" target="_blank" href="' . \get_the_author_meta( 'user_url', $author_id ) . '">' . \wp_parse_url( \get_the_author_meta( 'user_url', $author_id ), PHP_URL_HOST ) . '</a>',
ENT_QUOTES,
'<a rel="me" title="' . \esc_attr( \get_the_author_meta( 'user_url', $author_id ) ) . '" target="_blank" href="' . \get_the_author_meta( 'user_url', $author_id ) . '">' . \wp_parse_url( \get_the_author_meta( 'user_url', $author_id ), \PHP_URL_HOST ) . '</a>',
\ENT_QUOTES,
'UTF-8'
),
);
@ -92,10 +92,10 @@ $json = \apply_filters( 'activitypub_json_author_array', $json );
$options = 0;
// JSON_PRETTY_PRINT added in PHP 5.4
if ( \get_query_var( 'pretty' ) ) {
$options |= JSON_PRETTY_PRINT; // phpcs:ignore
$options |= \JSON_PRETTY_PRINT; // phpcs:ignore
}
$options |= JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT;
$options |= \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT;
/*
* Options to be passed to json_encode()

View File

@ -15,10 +15,10 @@ $json = \apply_filters( 'activitypub_json_post_array', $json );
$options = 0;
// JSON_PRETTY_PRINT added in PHP 5.4
if ( \get_query_var( 'pretty' ) ) {
$options |= JSON_PRETTY_PRINT; // phpcs:ignore
$options |= \JSON_PRETTY_PRINT; // phpcs:ignore
}
$options |= JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT;
$options |= \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_QUOT;
/*
* Options to be passed to json_encode()

View File

@ -14,27 +14,35 @@
<tbody>
<tr>
<th scope="row">
<?php esc_html_e( 'Post-Content', 'activitypub' ); ?>
<?php \esc_html_e( 'Post-Content', 'activitypub' ); ?>
</th>
<td>
<p>
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_title_link" value="title" <?php echo \checked( 'title', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Title and link', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Only the title and a link.', 'activitypub' ); ?></span>
</p>
</p>
<p>
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_excerpt" value="excerpt" <?php echo \checked( 'excerpt', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Excerpt', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'A content summary, shortened to 400 characters and without markup.', 'activitypub' ); ?></span>
</p>
<p>
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_content" value="content" <?php echo \checked( 'content', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Content (default)', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'The full content.', 'activitypub' ); ?></span>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php \esc_html_e( 'Backlink', 'activitypub' ); ?>
</th>
<td>
<p><label><input type="checkbox" name="activitypub_use_shortlink" id="activitypub_use_shortlink" value="1" <?php echo \checked( '1', \get_option( 'activitypub_use_shortlink', '0' ) ); ?> /> <?php \esc_html_e( 'Use the Shortlink instead of the permalink', 'activitypub' ); ?></label></p>
<p class="description"><?php \printf( esc_html( 'I can recommend %sHum%s, to prettify the Shortlinks', 'activitypub' ), '<a href="https://wordpress.org/plugins/hum/" target="_blank">', '</a>' ); ?></p>
<p>
<label><input type="radio" name="activitypub_post_content_type" id="activitypub_post_content_type_custom" value="custom" <?php echo \checked( 'custom', \get_option( 'activitypub_post_content_type', 'content' ) ); ?> /> <?php \esc_html_e( 'Custom', 'activitypub' ); ?></label> - <span class="description"><?php \esc_html_e( 'Use the text-area below, to customize your activities.', 'activitypub' ); ?></span>
</p>
<p>
<textarea name="activitypub_custom_post_content" id="activitypub_custom_post_content" rows="10" cols="50" class="large-text" placeholder="<?php echo ACTIVITYPUB_CUSTOM_POST_CONTENT; ?>"><?php echo \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT ); ?></textarea>
<div class="description">
<ul>
<li><code>%title%</code> - <?php \esc_html_e( 'The Post-Title.', 'activitypub' ); ?></li>
<li><code>%content%</code> - <?php \esc_html_e( 'The Post-Content.', 'activitypub' ); ?></li>
<li><code>%excerpt%</code> - <?php \esc_html_e( 'The Post-Excerpt (default 400 Chars).', 'activitypub' ); ?></li>
<li><code>%permalink%</code> - <?php \esc_html_e( 'The Post-Permalink.', 'activitypub' ); ?></li>
<li><code>%shortlink%</code> - <?php \printf( \esc_html( 'The Post-Shortlink. I can recommend %sHum%s, to prettify the Shortlinks', 'activitypub' ), '<a href="https://wordpress.org/plugins/hum/" target="_blank">', '</a>' ); ?></li>
<li><code>%hashtags%</code> - <?php \esc_html_e( 'The Tags as Hashtags.', 'activitypub' ); ?></li>
</ul>
<?php \printf( \__( '%sLet me know%s if you miss a template placeholder.', 'activitypub' ), '<a href="https://github.com/pfefferle/wordpress-activitypub/issues/new" target="_blank">', '</a>' ); ?>
</div>
</p>
</td>
</tr>
<tr>
@ -80,9 +88,15 @@
<p>
<label><input type="checkbox" name="activitypub_use_hashtags" id="activitypub_use_hashtags" value="1" <?php echo \checked( '1', \get_option( 'activitypub_use_hashtags', '1' ) ); ?> /> <?php \_e( 'Add hashtags in the content as native tags and replace the <code>#tag</code> with the tag-link.', 'activitypub' ); ?></label>
</p>
<p>
<label><input type="checkbox" name="activitypub_add_tags_as_hashtags" id="activitypub_add_tags_as_hashtags" value="1" <?php echo \checked( '1', \get_option( 'activitypub_add_tags_as_hashtags', '0' ) ); ?> /> <?php \_e( 'Add all tags as hashtags to the end of each activity.', 'activitypub' ); ?></label>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php \esc_html_e( 'HTML Whitelist', 'activitypub' ); ?>
</th>
<td>
<textarea name="activitypub_allowed_html" id="activitypub_allowed_html" rows="3" cols="50" class="large-text"><?php echo \get_option( 'activitypub_allowed_html', ACTIVITYPUB_ALLOWED_HTML ); ?></textarea>
<p class="description"><?php \_e( \sprintf( 'A list of HTML elements, you want to whitelist for your activities. <strong>Leave list empty to support all HTML elements.</strong> Default: <code>%s</code>.', \esc_html( ACTIVITYPUB_ALLOWED_HTML ) ), 'activitypub' ); ?></p>
</td>
</tr>
</tbody>
@ -94,20 +108,14 @@
<p><?php \esc_html_e( 'Server related settings.', 'activitypub' ); ?></p>
<?php
// load the existing blacklist from the WordPress options table
$activitypub_blacklist = \trim( \implode( PHP_EOL, \ActivityPub\get_blacklist() ), PHP_EOL );
?>
<table class="form-table">
<tbody>
<tr>
<th scope="row">
<?php \esc_html_e( 'Blacklist', 'activitypub' ); ?>
<?php \esc_html_e( 'Blocklist', 'activitypub' ); ?>
</th>
<td>
<textarea name="activitypub_blacklist" id="activitypub_blacklist" rows="10" cols="50" class="large-text"><?php echo $activitypub_blacklist; ?></textarea>
<p class="description"><?php \_e( 'A list of hosts, you want to block, one host per line. Please use only the host/domain of the server you want to block, without <code>http://</code> and without <code>www.</code>. For example <code>example.com</code>.', 'activitypub' ); ?></p>
<p class="description"><?php \printf( \__( 'To block servers, add the host of the server to the "<a href="%s">Disallowed Comment Keys</a>" list.', 'activitypub' ), admin_url( 'options-discussion.php#disallowed_keys' ) ); ?></p>
</td>
</tr>
</tbody>
@ -121,6 +129,6 @@
</form>
<p>
<small><?php _e( 'If you like this plugin, what about a small <a href="https://notiz.blog/donate">donation</a>?', 'activitypub' ); ?></small>
<small><?php \_e( 'If you like this plugin, what about a small <a href="https://notiz.blog/donate">donation</a>?', 'activitypub' ); ?></small>
</p>
</div>